forked from notaryproject/notary
/
default.go
240 lines (219 loc) · 7.1 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
package handlers
import (
"bytes"
"encoding/json"
"io"
"net/http"
"strings"
"github.com/Sirupsen/logrus"
ctxu "github.com/docker/distribution/context"
"github.com/gorilla/mux"
"golang.org/x/net/context"
"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 := vars["imageName"]
s := ctx.Value("metaStore")
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("cryptoService")
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 {
return errors.ErrMalformedUpload.WithDetail(nil)
}
var updates []storage.MetaUpdate
for {
part, err := reader.NextPart()
if err == io.EOF {
break
}
role := strings.TrimSuffix(part.FileName(), ".json")
if role == "" {
return errors.ErrNoFilename.WithDetail(nil)
} else if !data.ValidRole(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 {
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 {
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 {
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)
}
return nil
}
// 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 := vars["imageName"]
checksum := vars["checksum"]
tufRole := vars["tufRole"]
s := ctx.Value("metaStore")
store, ok := s.(storage.MetaStore)
if !ok {
return errors.ErrNoStorage.WithDetail(nil)
}
lastModified, output, err := getRole(ctx, store, gun, tufRole, checksum)
if err != nil {
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 {
logrus.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 {
s := ctx.Value("metaStore")
store, ok := s.(storage.MetaStore)
if !ok {
return errors.ErrNoStorage.WithDetail(nil)
}
vars := mux.Vars(r)
gun := vars["imageName"]
logger := ctxu.GetLoggerWithField(ctx, gun, "gun")
err := store.Delete(gun)
if err != nil {
logger.Error("500 DELETE repository")
return errors.ErrUnknown.WithDetail(err)
}
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 {
gun, ok := vars["imageName"]
if !ok || gun == "" {
return errors.ErrUnknown.WithDetail("no gun")
}
role, ok := vars["tufRole"]
if !ok || role == "" {
return errors.ErrUnknown.WithDetail("no role")
}
logger := ctxu.GetLoggerWithField(ctx, gun, "gun")
s := ctx.Value("metaStore")
store, ok := s.(storage.MetaStore)
if !ok || store == nil {
logger.Error("500 GET storage not configured")
return errors.ErrNoStorage.WithDetail(nil)
}
c := ctx.Value("cryptoService")
crypto, ok := c.(signed.CryptoService)
if !ok || crypto == nil {
logger.Error("500 GET crypto service not configured")
return errors.ErrNoCryptoService.WithDetail(nil)
}
algo := ctx.Value("keyAlgorithm")
keyAlgo, ok := algo.(string)
if !ok || keyAlgo == "" {
logger.Error("500 GET key algorithm not configured")
return errors.ErrNoKeyAlgorithm.WithDetail(nil)
}
keyAlgorithm := keyAlgo
var (
key data.PublicKey
err error
)
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.Errorf("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
}
// 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)
}