forked from notaryproject/notary
/
default.go
316 lines (287 loc) · 10.2 KB
/
default.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
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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
package handlers
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"strings"
ctxu "github.com/docker/distribution/context"
"github.com/gorilla/mux"
"golang.org/x/net/context"
"github.com/docker/notary"
"github.com/docker/notary/server/errors"
"github.com/docker/notary/server/snapshot"
"github.com/docker/notary/server/storage"
"github.com/docker/notary/server/timestamp"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/validation"
"github.com/docker/notary/utils"
)
// MainHandler is the default handler for the server
func MainHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
// For now it only supports `GET`
if r.Method != "GET" {
return errors.ErrGenericNotFound.WithDetail(nil)
}
if _, err := w.Write([]byte("{}")); err != nil {
return errors.ErrUnknown.WithDetail(err)
}
return nil
}
// AtomicUpdateHandler will accept multiple TUF files and ensure that the storage
// backend is atomically updated with all the new records.
func AtomicUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
defer r.Body.Close()
vars := mux.Vars(r)
return atomicUpdateHandler(ctx, w, r, vars)
}
func atomicUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
gun := data.GUN(vars["gun"])
s := ctx.Value(notary.CtxKeyMetaStore)
logger := ctxu.GetLoggerWithField(ctx, gun, "gun")
store, ok := s.(storage.MetaStore)
if !ok {
logger.Error("500 POST unable to retrieve storage")
return errors.ErrNoStorage.WithDetail(nil)
}
cryptoServiceVal := ctx.Value(notary.CtxKeyCryptoSvc)
cryptoService, ok := cryptoServiceVal.(signed.CryptoService)
if !ok {
logger.Error("500 POST unable to retrieve signing service")
return errors.ErrNoCryptoService.WithDetail(nil)
}
reader, err := r.MultipartReader()
if err != nil {
logger.Info("400 POST unable to parse TUF data")
return errors.ErrMalformedUpload.WithDetail(nil)
}
var updates []storage.MetaUpdate
for {
part, err := reader.NextPart()
if err == io.EOF {
break
}
role := data.RoleName(strings.TrimSuffix(part.FileName(), ".json"))
if role.String() == "" {
logger.Info("400 POST empty role")
return errors.ErrNoFilename.WithDetail(nil)
} else if !data.ValidRole(role) {
logger.Infof("400 POST invalid role: %s", role)
return errors.ErrInvalidRole.WithDetail(role)
}
meta := &data.SignedMeta{}
var input []byte
inBuf := bytes.NewBuffer(input)
dec := json.NewDecoder(io.TeeReader(part, inBuf))
err = dec.Decode(meta)
if err != nil {
logger.Info("400 POST malformed update JSON")
return errors.ErrMalformedJSON.WithDetail(nil)
}
version := meta.Signed.Version
updates = append(updates, storage.MetaUpdate{
Role: role,
Version: version,
Data: inBuf.Bytes(),
})
}
updates, err = validateUpdate(cryptoService, gun, updates, store)
if err != nil {
serializable, serializableError := validation.NewSerializableError(err)
if serializableError != nil {
logger.Info("400 POST error validating update")
return errors.ErrInvalidUpdate.WithDetail(nil)
}
return errors.ErrInvalidUpdate.WithDetail(serializable)
}
err = store.UpdateMany(gun, updates)
if err != nil {
// If we have an old version error, surface to user with error code
if _, ok := err.(storage.ErrOldVersion); ok {
logger.Info("400 POST old version error")
return errors.ErrOldVersion.WithDetail(err)
}
// More generic storage update error, possibly due to attempted rollback
logger.Errorf("500 POST error applying update request: %v", err)
return errors.ErrUpdating.WithDetail(nil)
}
logTS(logger, gun.String(), updates)
return nil
}
// logTS logs the timestamp update at Info level
func logTS(logger ctxu.Logger, gun string, updates []storage.MetaUpdate) {
for _, update := range updates {
if update.Role == data.CanonicalTimestampRole {
checksumBin := sha256.Sum256(update.Data)
checksum := hex.EncodeToString(checksumBin[:])
logger.Infof("updated %s to timestamp version %d, checksum %s", gun, update.Version, checksum)
break
}
}
}
// GetHandler returns the json for a specified role and GUN.
func GetHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
defer r.Body.Close()
vars := mux.Vars(r)
return getHandler(ctx, w, r, vars)
}
func getHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
gun := data.GUN(vars["gun"])
checksum := vars["checksum"]
version := vars["version"]
tufRole := vars["tufRole"]
s := ctx.Value(notary.CtxKeyMetaStore)
logger := ctxu.GetLoggerWithField(ctx, gun, "gun")
store, ok := s.(storage.MetaStore)
if !ok {
logger.Error("500 GET: no storage exists")
return errors.ErrNoStorage.WithDetail(nil)
}
lastModified, output, err := getRole(ctx, store, gun, data.RoleName(tufRole), checksum, version)
if err != nil {
logger.Infof("404 GET %s role", tufRole)
return err
}
if lastModified != nil {
// This shouldn't always be true, but in case it is nil, and the last modified headers
// are not set, the cache control handler should set the last modified date to the beginning
// of time.
utils.SetLastModifiedHeader(w.Header(), *lastModified)
} else {
logger.Warnf("Got bytes out for %s's %s (checksum: %s), but missing lastModified date",
gun, tufRole, checksum)
}
w.Write(output)
return nil
}
// DeleteHandler deletes all data for a GUN. A 200 responses indicates success.
func DeleteHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
gun := data.GUN(vars["gun"])
logger := ctxu.GetLoggerWithField(ctx, gun, "gun")
s := ctx.Value(notary.CtxKeyMetaStore)
store, ok := s.(storage.MetaStore)
if !ok {
logger.Error("500 DELETE repository: no storage exists")
return errors.ErrNoStorage.WithDetail(nil)
}
err := store.Delete(gun)
if err != nil {
logger.Error("500 DELETE repository")
return errors.ErrUnknown.WithDetail(err)
}
logger.Infof("trust data deleted for %s", gun)
return nil
}
// GetKeyHandler returns a public key for the specified role, creating a new key-pair
// it if it doesn't yet exist
func GetKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
defer r.Body.Close()
vars := mux.Vars(r)
return getKeyHandler(ctx, w, r, vars)
}
func getKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
role, gun, keyAlgorithm, store, crypto, err := setupKeyHandler(ctx, w, r, vars, http.MethodGet)
if err != nil {
return err
}
var key data.PublicKey
logger := ctxu.GetLoggerWithField(ctx, gun, "gun")
switch role {
case data.CanonicalTimestampRole:
key, err = timestamp.GetOrCreateTimestampKey(gun, store, crypto, keyAlgorithm)
case data.CanonicalSnapshotRole:
key, err = snapshot.GetOrCreateSnapshotKey(gun, store, crypto, keyAlgorithm)
default:
logger.Infof("400 GET %s key: %v", role, err)
return errors.ErrInvalidRole.WithDetail(role)
}
if err != nil {
logger.Errorf("500 GET %s key: %v", role, err)
return errors.ErrUnknown.WithDetail(err)
}
out, err := json.Marshal(key)
if err != nil {
logger.Errorf("500 GET %s key", role)
return errors.ErrUnknown.WithDetail(err)
}
logger.Debugf("200 GET %s key", role)
w.Write(out)
return nil
}
// RotateKeyHandler rotates the remote key for the specified role, returning the public key
func RotateKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
defer r.Body.Close()
vars := mux.Vars(r)
return rotateKeyHandler(ctx, w, r, vars)
}
func rotateKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
role, gun, keyAlgorithm, store, crypto, err := setupKeyHandler(ctx, w, r, vars, http.MethodPost)
if err != nil {
return err
}
var key data.PublicKey
logger := ctxu.GetLoggerWithField(ctx, gun, "gun")
switch role {
case data.CanonicalTimestampRole:
key, err = timestamp.RotateTimestampKey(gun, store, crypto, keyAlgorithm)
case data.CanonicalSnapshotRole:
key, err = snapshot.RotateSnapshotKey(gun, store, crypto, keyAlgorithm)
default:
logger.Infof("400 POST %s key: %v", role, err)
return errors.ErrInvalidRole.WithDetail(role)
}
if err != nil {
logger.Errorf("500 POST %s key: %v", role, err)
return errors.ErrUnknown.WithDetail(err)
}
out, err := json.Marshal(key)
if err != nil {
logger.Errorf("500 POST %s key", role)
return errors.ErrUnknown.WithDetail(err)
}
logger.Debugf("200 POST %s key", role)
w.Write(out)
return nil
}
// To be called before getKeyHandler or rotateKeyHandler
func setupKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string, actionVerb string) (data.RoleName, data.GUN, string, storage.MetaStore, signed.CryptoService, error) {
gun := data.GUN(vars["gun"])
logger := ctxu.GetLoggerWithField(ctx, gun, "gun")
if gun == "" {
logger.Infof("400 %s no gun in request", actionVerb)
return "", "", "", nil, nil, errors.ErrUnknown.WithDetail("no gun")
}
role := data.RoleName(vars["tufRole"])
if role == "" {
logger.Infof("400 %s no role in request", actionVerb)
return "", "", "", nil, nil, errors.ErrUnknown.WithDetail("no role")
}
s := ctx.Value(notary.CtxKeyMetaStore)
store, ok := s.(storage.MetaStore)
if !ok || store == nil {
logger.Errorf("500 %s storage not configured", actionVerb)
return "", "", "", nil, nil, errors.ErrNoStorage.WithDetail(nil)
}
c := ctx.Value(notary.CtxKeyCryptoSvc)
crypto, ok := c.(signed.CryptoService)
if !ok || crypto == nil {
logger.Errorf("500 %s crypto service not configured", actionVerb)
return "", "", "", nil, nil, errors.ErrNoCryptoService.WithDetail(nil)
}
algo := ctx.Value(notary.CtxKeyKeyAlgo)
keyAlgo, ok := algo.(string)
if !ok || keyAlgo == "" {
logger.Errorf("500 %s key algorithm not configured", actionVerb)
return "", "", "", nil, nil, errors.ErrNoKeyAlgorithm.WithDetail(nil)
}
return role, gun, keyAlgo, store, crypto, nil
}
// NotFoundHandler is used as a generic catch all handler to return the ErrMetadataNotFound
// 404 response
func NotFoundHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
return errors.ErrMetadataNotFound.WithDetail(nil)
}