-
Notifications
You must be signed in to change notification settings - Fork 509
/
validation.go
230 lines (204 loc) · 8.05 KB
/
validation.go
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
package handlers
import (
"fmt"
"path"
"sort"
"github.com/Sirupsen/logrus"
"github.com/docker/go/canonical/json"
"github.com/docker/notary/server/storage"
"github.com/docker/notary/trustpinning"
"github.com/docker/notary/tuf"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/utils"
"github.com/docker/notary/tuf/validation"
)
// validateUpload checks that the updates being pushed
// are semantically correct and the signatures are correct
// A list of possibly modified updates are returned if all
// validation was successful. This allows the snapshot to be
// created and added if snapshotting has been delegated to the
// server
func validateUpdate(cs signed.CryptoService, gun string, updates []storage.MetaUpdate, store storage.MetaStore) ([]storage.MetaUpdate, error) {
// some delegated targets role may be invalid based on other updates
// that have been made by other clients. We'll rebuild the slice of
// updates with only the things we should actually update
updatesToApply := make([]storage.MetaUpdate, 0, len(updates))
roles := make(map[string]storage.MetaUpdate)
for _, v := range updates {
roles[v.Role] = v
}
builder := tuf.NewRepoBuilder(gun, cs, trustpinning.TrustPinConfig{})
if err := loadFromStore(gun, data.CanonicalRootRole, builder, store); err != nil {
if _, ok := err.(storage.ErrNotFound); !ok {
return nil, err
}
}
if rootUpdate, ok := roles[data.CanonicalRootRole]; ok {
builder = builder.BootstrapNewBuilder()
if err := builder.Load(data.CanonicalRootRole, rootUpdate.Data, 1, false); err != nil {
return nil, validation.ErrBadRoot{Msg: err.Error()}
}
logrus.Debug("Successfully validated root")
updatesToApply = append(updatesToApply, rootUpdate)
} else if !builder.IsLoaded(data.CanonicalRootRole) {
return nil, validation.ErrValidation{Msg: "no pre-existing root and no root provided in update."}
}
targetsToUpdate, err := loadAndValidateTargets(gun, builder, roles, store)
if err != nil {
return nil, err
}
updatesToApply = append(updatesToApply, targetsToUpdate...)
// there's no need to load files from the database if no targets etc...
// were uploaded because that means they haven't been updated and
// the snapshot will already contain the correct hashes and sizes for
// those targets (incl. delegated targets)
logrus.Debug("Successfully validated targets")
// At this point, root and targets must have been loaded into the repo
if snapshotUpdate, ok := roles[data.CanonicalSnapshotRole]; ok {
if err := builder.Load(data.CanonicalSnapshotRole, snapshotUpdate.Data, 1, false); err != nil {
return nil, validation.ErrBadSnapshot{Msg: err.Error()}
}
logrus.Debug("Successfully validated snapshot")
updatesToApply = append(updatesToApply, roles[data.CanonicalSnapshotRole])
} else {
// Check:
// - we have a snapshot key
// - it matches a snapshot key signed into the root.json
// Then:
// - generate a new snapshot
// - add it to the updates
update, err := generateSnapshot(gun, builder, store)
if err != nil {
return nil, err
}
updatesToApply = append(updatesToApply, *update)
}
// generate a timestamp immediately
update, err := generateTimestamp(gun, builder, store)
if err != nil {
return nil, err
}
return append(updatesToApply, *update), nil
}
func loadAndValidateTargets(gun string, builder tuf.RepoBuilder, roles map[string]storage.MetaUpdate, store storage.MetaStore) ([]storage.MetaUpdate, error) {
targetsRoles := make(utils.RoleList, 0)
for role := range roles {
if role == data.CanonicalTargetsRole || data.IsDelegation(role) {
targetsRoles = append(targetsRoles, role)
}
}
// N.B. RoleList sorts paths with fewer segments first.
// By sorting, we'll always process shallower targets updates before deeper
// ones (i.e. we'll load and validate targets before targets/foo). This
// helps ensure we only load from storage when necessary in a cleaner way.
sort.Sort(targetsRoles)
updatesToApply := make([]storage.MetaUpdate, 0, len(targetsRoles))
for _, roleName := range targetsRoles {
// don't load parent if current role is "targets",
// we must load all ancestor roles, starting from `targets` and working down,
// for delegations to validate the full parent chain
var parentsToLoad []string
ancestorRole := roleName
for ancestorRole != data.CanonicalTargetsRole {
ancestorRole = path.Dir(ancestorRole)
if !builder.IsLoaded(ancestorRole) {
parentsToLoad = append(parentsToLoad, ancestorRole)
}
}
for i := len(parentsToLoad) - 1; i >= 0; i-- {
if err := loadFromStore(gun, parentsToLoad[i], builder, store); err != nil {
// if the parent doesn't exist, just keep going - loading the role will eventually fail
// due to it being an invalid role
if _, ok := err.(storage.ErrNotFound); !ok {
return nil, err
}
}
}
if err := builder.Load(roleName, roles[roleName].Data, 1, false); err != nil {
logrus.Error("ErrBadTargets: ", err.Error())
return nil, validation.ErrBadTargets{Msg: err.Error()}
}
updatesToApply = append(updatesToApply, roles[roleName])
}
return updatesToApply, nil
}
// generateSnapshot generates a new snapshot from the previous one in the store - this assumes all
// the other roles except timestamp have already been set on the repo, and will set the generated
// snapshot on the repo as well
func generateSnapshot(gun string, builder tuf.RepoBuilder, store storage.MetaStore) (*storage.MetaUpdate, error) {
var prev *data.SignedSnapshot
_, currentJSON, err := store.GetCurrent(gun, data.CanonicalSnapshotRole)
if err == nil {
prev = new(data.SignedSnapshot)
if err = json.Unmarshal(currentJSON, prev); err != nil {
logrus.Error("Failed to unmarshal existing snapshot for GUN ", gun)
return nil, err
}
}
if _, ok := err.(storage.ErrNotFound); !ok && err != nil {
return nil, err
}
meta, ver, err := builder.GenerateSnapshot(prev)
switch err.(type) {
case nil:
return &storage.MetaUpdate{
Role: data.CanonicalSnapshotRole,
Version: ver,
Data: meta,
}, nil
case signed.ErrInsufficientSignatures, signed.ErrNoKeys, signed.ErrRoleThreshold:
// If we cannot sign the snapshot, then we don't have keys for the snapshot,
// and the client should have submitted a snapshot
return nil, validation.ErrBadHierarchy{
Missing: data.CanonicalSnapshotRole,
Msg: "no snapshot was included in update and server does not hold current snapshot key for repository"}
default:
return nil, validation.ErrValidation{Msg: err.Error()}
}
}
// generateTimestamp generates a new timestamp from the previous one in the store - this assumes all
// the other roles have already been set on the repo, and will set the generated timestamp on the repo as well
func generateTimestamp(gun string, builder tuf.RepoBuilder, store storage.MetaStore) (*storage.MetaUpdate, error) {
var prev *data.SignedTimestamp
_, currentJSON, err := store.GetCurrent(gun, data.CanonicalTimestampRole)
switch err.(type) {
case nil:
prev = new(data.SignedTimestamp)
if err := json.Unmarshal(currentJSON, prev); err != nil {
logrus.Error("Failed to unmarshal existing timestamp for GUN ", gun)
return nil, err
}
case storage.ErrNotFound:
break // this is the first timestamp ever for the repo
default:
return nil, err
}
meta, ver, err := builder.GenerateTimestamp(prev)
switch err.(type) {
case nil:
return &storage.MetaUpdate{
Role: data.CanonicalTimestampRole,
Version: ver,
Data: meta,
}, nil
case signed.ErrInsufficientSignatures, signed.ErrNoKeys:
// If we cannot sign the timestamp, then we don't have keys for the timestamp,
// and the client screwed up their root
return nil, validation.ErrBadRoot{
Msg: fmt.Sprintf("no timestamp keys exist on the server"),
}
default:
return nil, validation.ErrValidation{Msg: err.Error()}
}
}
func loadFromStore(gun, roleName string, builder tuf.RepoBuilder, store storage.MetaStore) error {
_, metaJSON, err := store.GetCurrent(gun, roleName)
if err != nil {
return err
}
if err := builder.Load(roleName, metaJSON, 1, true); err != nil {
return err
}
return nil
}