mirror of https://github.com/docker/docs.git
commit
dd69872bb6
|
@ -7,15 +7,17 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
ctxu "github.com/docker/distribution/context"
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
ctxu "github.com/docker/distribution/context"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary/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"
|
||||
)
|
||||
|
||||
// MainHandler is the default handler for the server
|
||||
|
@ -35,13 +37,23 @@ func MainHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) er
|
|||
// 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")
|
||||
store, ok := s.(storage.MetaStore)
|
||||
if !ok {
|
||||
return errors.ErrNoStorage.WithDetail(nil)
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
gun := vars["imageName"]
|
||||
cryptoServiceVal := ctx.Value("cryptoService")
|
||||
cryptoService, ok := cryptoServiceVal.(signed.CryptoService)
|
||||
if !ok {
|
||||
return errors.ErrNoCryptoService.WithDetail(nil)
|
||||
}
|
||||
|
||||
reader, err := r.MultipartReader()
|
||||
if err != nil {
|
||||
return errors.ErrMalformedUpload.WithDetail(nil)
|
||||
|
@ -73,7 +85,8 @@ func AtomicUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Req
|
|||
Data: inBuf.Bytes(),
|
||||
})
|
||||
}
|
||||
if err = validateUpdate(gun, updates, store); err != nil {
|
||||
updates, err = validateUpdate(cryptoService, gun, updates, store)
|
||||
if err != nil {
|
||||
return errors.ErrMalformedUpload.WithDetail(err)
|
||||
}
|
||||
err = store.UpdateMany(gun, updates)
|
||||
|
@ -85,24 +98,33 @@ func AtomicUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Req
|
|||
|
||||
// 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"]
|
||||
tufRole := vars["tufRole"]
|
||||
s := ctx.Value("metaStore")
|
||||
store, ok := s.(storage.MetaStore)
|
||||
if !ok {
|
||||
return errors.ErrNoStorage.WithDetail(nil)
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
gun := vars["imageName"]
|
||||
tufRole := vars["tufRole"]
|
||||
|
||||
logger := ctxu.GetLoggerWithFields(ctx, map[string]interface{}{"gun": gun, "tufRole": tufRole})
|
||||
|
||||
if data.CanonicalRole(tufRole) == data.CanonicalTimestampRole {
|
||||
switch data.CanonicalRole(tufRole) {
|
||||
case data.CanonicalTimestampRole:
|
||||
return getTimestamp(ctx, w, logger, store, gun)
|
||||
case data.CanonicalSnapshotRole:
|
||||
return getSnapshot(ctx, w, logger, store, gun)
|
||||
}
|
||||
|
||||
out, err := store.GetCurrent(gun, tufRole)
|
||||
if err != nil {
|
||||
if _, ok := err.(*storage.ErrNotFound); ok {
|
||||
logrus.Error(gun + ":" + tufRole)
|
||||
return errors.ErrMetadataNotFound.WithDetail(nil)
|
||||
}
|
||||
logger.Error("500 GET")
|
||||
|
@ -161,6 +183,31 @@ func getTimestamp(ctx context.Context, w http.ResponseWriter, logger ctxu.Logger
|
|||
return nil
|
||||
}
|
||||
|
||||
// getTimestampHandler returns a timestamp.json given a GUN
|
||||
func getSnapshot(ctx context.Context, w http.ResponseWriter, logger ctxu.Logger, store storage.MetaStore, gun string) error {
|
||||
cryptoServiceVal := ctx.Value("cryptoService")
|
||||
cryptoService, ok := cryptoServiceVal.(signed.CryptoService)
|
||||
if !ok {
|
||||
return errors.ErrNoCryptoService.WithDetail(nil)
|
||||
}
|
||||
|
||||
out, err := snapshot.GetOrCreateSnapshot(gun, store, cryptoService)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *storage.ErrNoKey, *storage.ErrNotFound:
|
||||
logger.Error("404 GET snapshot")
|
||||
return errors.ErrMetadataNotFound.WithDetail(nil)
|
||||
default:
|
||||
logger.Error("500 GET snapshot")
|
||||
return errors.ErrUnknown.WithDetail(err)
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debug("200 GET snapshot")
|
||||
w.Write(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTimestampKeyHandler returns a timestamp public key, creating a new key-pair
|
||||
// it if it doesn't yet exist
|
||||
func GetTimestampKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/notary/server/storage"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
|
||||
"github.com/docker/notary/tuf/testutils"
|
||||
"github.com/docker/notary/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMainHandlerGet(t *testing.T) {
|
||||
|
@ -38,3 +45,145 @@ func TestMainHandlerNotGet(t *testing.T) {
|
|||
t.Fatalf("Expected 404, received %d", res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetHandlerRoot(t *testing.T) {
|
||||
store := storage.NewMemStorage()
|
||||
_, repo, _ := testutils.EmptyRepo()
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "metaStore", store)
|
||||
|
||||
root, err := repo.SignRoot(data.DefaultExpires("root"))
|
||||
rootJSON, err := json.Marshal(root)
|
||||
assert.NoError(t, err)
|
||||
store.UpdateCurrent("gun", storage.MetaUpdate{Role: "root", Version: 1, Data: rootJSON})
|
||||
|
||||
req := &http.Request{
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer(nil)),
|
||||
}
|
||||
|
||||
vars := map[string]string{
|
||||
"imageName": "gun",
|
||||
"tufRole": "root",
|
||||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
err = getHandler(ctx, rw, req, vars)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetHandlerTimestamp(t *testing.T) {
|
||||
store := storage.NewMemStorage()
|
||||
_, repo, crypto := testutils.EmptyRepo()
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "metaStore", store)
|
||||
ctx = context.WithValue(ctx, "cryptoService", crypto)
|
||||
|
||||
sn, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
|
||||
snJSON, err := json.Marshal(sn)
|
||||
assert.NoError(t, err)
|
||||
store.UpdateCurrent("gun", storage.MetaUpdate{Role: "snapshot", Version: 1, Data: snJSON})
|
||||
|
||||
ts, err := repo.SignTimestamp(data.DefaultExpires("timestamp"))
|
||||
tsJSON, err := json.Marshal(ts)
|
||||
assert.NoError(t, err)
|
||||
store.UpdateCurrent("gun", storage.MetaUpdate{Role: "timestamp", Version: 1, Data: tsJSON})
|
||||
|
||||
req := &http.Request{
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer(nil)),
|
||||
}
|
||||
|
||||
vars := map[string]string{
|
||||
"imageName": "gun",
|
||||
"tufRole": "timestamp",
|
||||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
err = getHandler(ctx, rw, req, vars)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetHandlerSnapshot(t *testing.T) {
|
||||
store := storage.NewMemStorage()
|
||||
_, repo, crypto := testutils.EmptyRepo()
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "metaStore", store)
|
||||
ctx = context.WithValue(ctx, "cryptoService", crypto)
|
||||
|
||||
sn, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
|
||||
snJSON, err := json.Marshal(sn)
|
||||
assert.NoError(t, err)
|
||||
store.UpdateCurrent("gun", storage.MetaUpdate{Role: "snapshot", Version: 1, Data: snJSON})
|
||||
|
||||
req := &http.Request{
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer(nil)),
|
||||
}
|
||||
|
||||
vars := map[string]string{
|
||||
"imageName": "gun",
|
||||
"tufRole": "snapshot",
|
||||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
err = getHandler(ctx, rw, req, vars)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetHandler404(t *testing.T) {
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "metaStore", store)
|
||||
|
||||
req := &http.Request{
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer(nil)),
|
||||
}
|
||||
|
||||
vars := map[string]string{
|
||||
"imageName": "gun",
|
||||
"tufRole": "root",
|
||||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
err := getHandler(ctx, rw, req, vars)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetHandlerNilData(t *testing.T) {
|
||||
store := storage.NewMemStorage()
|
||||
store.UpdateCurrent("gun", storage.MetaUpdate{Role: "root", Version: 1, Data: nil})
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "metaStore", store)
|
||||
|
||||
req := &http.Request{
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer(nil)),
|
||||
}
|
||||
|
||||
vars := map[string]string{
|
||||
"imageName": "gun",
|
||||
"tufRole": "root",
|
||||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
err := getHandler(ctx, rw, req, vars)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetHandlerNoStorage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
req := &http.Request{
|
||||
Body: ioutil.NopCloser(bytes.NewBuffer(nil)),
|
||||
}
|
||||
|
||||
err := GetHandler(ctx, nil, req)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// VALIDATION ERRORS:
|
||||
|
||||
// ErrValidation represents a general validation error
|
||||
type ErrValidation struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrValidation) Error() string {
|
||||
return fmt.Sprintf("An error occurred during validation: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadHierarchy represents a missing snapshot at this current time.
|
||||
// When delegations are implemented it will also represent a missing
|
||||
// delegation parent
|
||||
type ErrBadHierarchy struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadHierarchy) Error() string {
|
||||
return fmt.Sprintf("Hierarchy of updates in incorrect: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadRoot represents a failure validating the root
|
||||
type ErrBadRoot struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadRoot) Error() string {
|
||||
return fmt.Sprintf("The root being updated is invalid: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadTargets represents a failure to validate a targets (incl delegations)
|
||||
type ErrBadTargets struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadTargets) Error() string {
|
||||
return fmt.Sprintf("The targets being updated is invalid: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadSnapshot represents a failure to validate the snapshot
|
||||
type ErrBadSnapshot struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadSnapshot) Error() string {
|
||||
return fmt.Sprintf("The snapshot being updated is invalid: %s", err.msg)
|
||||
}
|
||||
|
||||
// END VALIDATION ERRORS
|
|
@ -7,68 +7,25 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/docker/notary/server/storage"
|
||||
"github.com/docker/notary/tuf"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary/server/storage"
|
||||
"github.com/docker/notary/tuf/keys"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
"github.com/docker/notary/tuf/utils"
|
||||
)
|
||||
|
||||
// ErrValidation represents a general validation error
|
||||
type ErrValidation struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrValidation) Error() string {
|
||||
return fmt.Sprintf("An error occurred during validation: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadHierarchy represents a missing snapshot at this current time.
|
||||
// When delegations are implemented it will also represent a missing
|
||||
// delegation parent
|
||||
type ErrBadHierarchy struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadHierarchy) Error() string {
|
||||
return fmt.Sprintf("Hierarchy of updates in incorrect: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadRoot represents a failure validating the root
|
||||
type ErrBadRoot struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadRoot) Error() string {
|
||||
return fmt.Sprintf("The root being updated is invalid: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadTargets represents a failure to validate a targets (incl delegations)
|
||||
type ErrBadTargets struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadTargets) Error() string {
|
||||
return fmt.Sprintf("The targets being updated is invalid: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadSnapshot represents a failure to validate the snapshot
|
||||
type ErrBadSnapshot struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadSnapshot) Error() string {
|
||||
return fmt.Sprintf("The snapshot being updated is invalid: %s", err.msg)
|
||||
}
|
||||
|
||||
// validateUpload checks that the updates being pushed
|
||||
// are semantically correct and the signatures are correct
|
||||
func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.MetaStore) error {
|
||||
// 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) {
|
||||
kdb := keys.NewDB()
|
||||
repo := tuf.NewRepo(kdb, nil)
|
||||
repo := tuf.NewRepo(kdb, cs)
|
||||
rootRole := data.RoleName(data.CanonicalRootRole)
|
||||
targetsRole := data.RoleName(data.CanonicalTargetsRole)
|
||||
snapshotRole := data.RoleName(data.CanonicalSnapshotRole)
|
||||
|
@ -78,11 +35,6 @@ func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.Meta
|
|||
for _, v := range updates {
|
||||
roles[v.Role] = v
|
||||
}
|
||||
if err := hierarchyOK(roles); err != nil {
|
||||
logrus.Error("ErrBadHierarchy: ", err.Error())
|
||||
return ErrBadHierarchy{msg: err.Error()}
|
||||
}
|
||||
logrus.Debug("Successfully validated hierarchy")
|
||||
|
||||
var root *data.SignedRoot
|
||||
oldRootJSON, err := store.GetCurrent(gun, rootRole)
|
||||
|
@ -90,33 +42,33 @@ func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.Meta
|
|||
// problem with storage. No expectation we can
|
||||
// write if we can't read so bail.
|
||||
logrus.Error("error reading previous root: ", err.Error())
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if rootUpdate, ok := roles[rootRole]; ok {
|
||||
// if root is present, validate its integrity, possibly
|
||||
// against a previous root
|
||||
if root, err = validateRoot(gun, oldRootJSON, rootUpdate.Data, store); err != nil {
|
||||
logrus.Error("ErrBadRoot: ", err.Error())
|
||||
return ErrBadRoot{msg: err.Error()}
|
||||
return nil, ErrBadRoot{msg: err.Error()}
|
||||
}
|
||||
|
||||
// setting root will update keys db
|
||||
if err = repo.SetRoot(root); err != nil {
|
||||
logrus.Error("ErrValidation: ", err.Error())
|
||||
return ErrValidation{msg: err.Error()}
|
||||
return nil, ErrValidation{msg: err.Error()}
|
||||
}
|
||||
logrus.Debug("Successfully validated root")
|
||||
} else {
|
||||
if oldRootJSON == nil {
|
||||
return ErrValidation{msg: "no pre-existing root and no root provided in update."}
|
||||
return nil, ErrValidation{msg: "no pre-existing root and no root provided in update."}
|
||||
}
|
||||
parsedOldRoot := &data.SignedRoot{}
|
||||
if err := json.Unmarshal(oldRootJSON, parsedOldRoot); err != nil {
|
||||
return ErrValidation{msg: "pre-existing root is corrupted and no root provided in update."}
|
||||
return nil, ErrValidation{msg: "pre-existing root is corrupted and no root provided in update."}
|
||||
}
|
||||
if err = repo.SetRoot(parsedOldRoot); err != nil {
|
||||
logrus.Error("ErrValidation: ", err.Error())
|
||||
return ErrValidation{msg: err.Error()}
|
||||
return nil, ErrValidation{msg: err.Error()}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,32 +77,139 @@ func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.Meta
|
|||
if _, ok := roles[targetsRole]; ok {
|
||||
if t, err = validateTargets(targetsRole, roles, kdb); err != nil {
|
||||
logrus.Error("ErrBadTargets: ", err.Error())
|
||||
return ErrBadTargets{msg: err.Error()}
|
||||
return nil, ErrBadTargets{msg: err.Error()}
|
||||
}
|
||||
repo.SetTargets(targetsRole, t)
|
||||
}
|
||||
logrus.Debug("Successfully validated targets")
|
||||
|
||||
var oldSnap *data.SignedSnapshot
|
||||
oldSnapJSON, err := store.GetCurrent(gun, snapshotRole)
|
||||
if _, ok := err.(*storage.ErrNotFound); err != nil && !ok {
|
||||
// problem with storage. No expectation we can
|
||||
// write if we can't read so bail.
|
||||
logrus.Error("error reading previous snapshot: ", err.Error())
|
||||
return err
|
||||
} else if err == nil {
|
||||
oldSnap = &data.SignedSnapshot{}
|
||||
if err := json.Unmarshal(oldSnapJSON, oldSnap); err != nil {
|
||||
oldSnap = nil
|
||||
if _, ok := roles[snapshotRole]; ok {
|
||||
var oldSnap *data.SignedSnapshot
|
||||
oldSnapJSON, err := store.GetCurrent(gun, snapshotRole)
|
||||
if _, ok := err.(*storage.ErrNotFound); err != nil && !ok {
|
||||
// problem with storage. No expectation we can
|
||||
// write if we can't read so bail.
|
||||
logrus.Error("error reading previous snapshot: ", err.Error())
|
||||
return nil, err
|
||||
} else if err == nil {
|
||||
oldSnap = &data.SignedSnapshot{}
|
||||
if err := json.Unmarshal(oldSnapJSON, oldSnap); err != nil {
|
||||
oldSnap = nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateSnapshot(snapshotRole, oldSnap, roles[snapshotRole], roles, kdb); err != nil {
|
||||
logrus.Error("ErrBadSnapshot: ", err.Error())
|
||||
return nil, ErrBadSnapshot{msg: err.Error()}
|
||||
}
|
||||
logrus.Debug("Successfully validated snapshot")
|
||||
} 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
|
||||
err := prepRepo(gun, repo, store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
update, err := generateSnapshot(gun, kdb, repo, store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updates = append(updates, *update)
|
||||
}
|
||||
return updates, nil
|
||||
}
|
||||
|
||||
func prepRepo(gun string, repo *tuf.Repo, store storage.MetaStore) error {
|
||||
if repo.Root == nil {
|
||||
rootJSON, err := store.GetCurrent(gun, data.CanonicalRootRole)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not load repo for snapshot generation: %v", err)
|
||||
}
|
||||
root := &data.SignedRoot{}
|
||||
err = json.Unmarshal(rootJSON, root)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not load repo for snapshot generation: %v", err)
|
||||
}
|
||||
repo.SetRoot(root)
|
||||
}
|
||||
if repo.Targets[data.CanonicalTargetsRole] == nil {
|
||||
targetsJSON, err := store.GetCurrent(gun, data.CanonicalTargetsRole)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not load repo for snapshot generation: %v", err)
|
||||
}
|
||||
targets := &data.SignedTargets{}
|
||||
err = json.Unmarshal(targetsJSON, targets)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not load repo for snapshot generation: %v", err)
|
||||
}
|
||||
repo.SetTargets(data.CanonicalTargetsRole, targets)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateSnapshot(gun string, kdb *keys.KeyDB, repo *tuf.Repo, store storage.MetaStore) (*storage.MetaUpdate, error) {
|
||||
role := kdb.GetRole(data.RoleName(data.CanonicalSnapshotRole))
|
||||
if role == nil {
|
||||
return nil, ErrBadRoot{msg: "root did not include snapshot role"}
|
||||
}
|
||||
|
||||
if err := validateSnapshot(snapshotRole, oldSnap, roles[snapshotRole], roles, kdb); err != nil {
|
||||
logrus.Error("ErrBadSnapshot: ", err.Error())
|
||||
return ErrBadSnapshot{msg: err.Error()}
|
||||
algo, keyBytes, err := store.GetKey(gun, data.CanonicalSnapshotRole)
|
||||
if role == nil {
|
||||
return nil, ErrBadRoot{msg: "root did not include snapshot key"}
|
||||
}
|
||||
logrus.Debug("Successfully validated snapshot")
|
||||
return nil
|
||||
foundK := data.NewPublicKey(algo, keyBytes)
|
||||
|
||||
validKey := false
|
||||
for _, id := range role.KeyIDs {
|
||||
if id == foundK.ID() {
|
||||
validKey = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validKey {
|
||||
return nil, ErrBadHierarchy{msg: "no snapshot was included in update and server does not hold current snapshot key for repository"}
|
||||
}
|
||||
|
||||
currentJSON, err := store.GetCurrent(gun, data.CanonicalSnapshotRole)
|
||||
if err != nil {
|
||||
if _, ok := err.(*storage.ErrNotFound); !ok {
|
||||
return nil, fmt.Errorf("could not retrieve previous snapshot: %v", err)
|
||||
}
|
||||
}
|
||||
var sn *data.SignedSnapshot
|
||||
if currentJSON != nil {
|
||||
sn = new(data.SignedSnapshot)
|
||||
err := json.Unmarshal(currentJSON, sn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not retrieve previous snapshot: %v", err)
|
||||
}
|
||||
err = repo.SetSnapshot(sn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load previous snapshot: %v", err)
|
||||
}
|
||||
} else {
|
||||
err := repo.InitSnapshot()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not initialize snapshot: %v", err)
|
||||
}
|
||||
}
|
||||
sgnd, err := repo.SignSnapshot(data.DefaultExpires(data.CanonicalSnapshotRole))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not sign snapshot: %v", err)
|
||||
}
|
||||
sgndJSON, err := json.Marshal(sgnd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not save snapshot: %v", err)
|
||||
}
|
||||
return &storage.MetaUpdate{
|
||||
Role: data.CanonicalSnapshotRole,
|
||||
Version: repo.Snapshot.Signed.Version,
|
||||
Data: sgndJSON,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func validateSnapshot(role string, oldSnap *data.SignedSnapshot, snapUpdate storage.MetaUpdate, roles map[string]storage.MetaUpdate, kdb *keys.KeyDB) error {
|
||||
|
@ -234,26 +293,6 @@ func validateTargets(role string, roles map[string]storage.MetaUpdate, kdb *keys
|
|||
return t, nil
|
||||
}
|
||||
|
||||
// check the snapshot is present. If it is, the hierarchy
|
||||
// of the update is OK. This seems like a simplistic check
|
||||
// but is completely sufficient for all possible use cases:
|
||||
// 1. the user is updating only the snapshot.
|
||||
// 2. the user is updating a targets (incl. delegations) or
|
||||
// root metadata. This requires they also provide a new
|
||||
// snapshot.
|
||||
// N.B. users should never be updating timestamps. The server
|
||||
// always handles timestamping. If the user does send a
|
||||
// timestamp, the server will replace it on next
|
||||
// GET timestamp.jsonshould it detect the current
|
||||
// snapshot has a different hash to the one in the timestamp.
|
||||
func hierarchyOK(roles map[string]storage.MetaUpdate) error {
|
||||
snapshotRole := data.RoleName(data.CanonicalSnapshotRole)
|
||||
if _, ok := roles[snapshotRole]; !ok {
|
||||
return errors.New("snapshot missing from update")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateRoot(gun string, oldRoot, newRoot []byte, store storage.MetaStore) (
|
||||
*data.SignedRoot, error) {
|
||||
|
||||
|
|
|
@ -2,10 +2,12 @@ package handlers
|
|||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/docker/notary/tuf"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/keys"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
|
@ -65,7 +67,7 @@ func getUpdates(r, tg, sn, ts *data.Signed) (
|
|||
}
|
||||
|
||||
func TestValidateEmptyNew(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -76,12 +78,12 @@ func TestValidateEmptyNew(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateNoNewRoot(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -93,12 +95,12 @@ func TestValidateNoNewRoot(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateNoNewTargets(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -110,12 +112,12 @@ func TestValidateNoNewTargets(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateOnlySnapshot(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -129,12 +131,12 @@ func TestValidateOnlySnapshot(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{snapshot}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateOldRoot(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -146,7 +148,7 @@ func TestValidateOldRoot(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
|
@ -191,12 +193,12 @@ func TestValidateRootRotation(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(crypto, "testGUN", updates, store)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateNoRoot(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -207,13 +209,13 @@ func TestValidateNoRoot(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrValidation{}, err)
|
||||
}
|
||||
|
||||
func TestValidateSnapshotMissing(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -224,16 +226,226 @@ func TestValidateSnapshotMissing(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadHierarchy{}, err)
|
||||
}
|
||||
|
||||
func TestValidateSnapshotGenerateNoPrev(t *testing.T) {
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
snapRole := kdb.GetRole(data.CanonicalSnapshotRole)
|
||||
|
||||
for _, id := range snapRole.KeyIDs {
|
||||
k := kdb.GetKey(id)
|
||||
assert.NotNil(t, k)
|
||||
err := store.SetKey("testGUN", data.CanonicalSnapshotRole, k.Algorithm(), k.Public())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
assert.NoError(t, err)
|
||||
root, targets, _, _, err := getUpdates(r, tg, sn, ts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
updates := []storage.MetaUpdate{root, targets}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestValidateSnapshotGenerateWithPrev(t *testing.T) {
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
snapRole := kdb.GetRole(data.CanonicalSnapshotRole)
|
||||
|
||||
for _, id := range snapRole.KeyIDs {
|
||||
k := kdb.GetKey(id)
|
||||
assert.NotNil(t, k)
|
||||
err := store.SetKey("testGUN", data.CanonicalSnapshotRole, k.Algorithm(), k.Public())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
assert.NoError(t, err)
|
||||
root, targets, snapshot, _, err := getUpdates(r, tg, sn, ts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
updates := []storage.MetaUpdate{root, targets}
|
||||
|
||||
// set the current snapshot in the store manually so we find it when generating
|
||||
// the next version
|
||||
store.UpdateCurrent("testGUN", snapshot)
|
||||
|
||||
prev, err := data.SnapshotFromSigned(sn)
|
||||
assert.NoError(t, err)
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
updates, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, u := range updates {
|
||||
if u.Role == data.CanonicalSnapshotRole {
|
||||
curr := &data.SignedSnapshot{}
|
||||
err = json.Unmarshal(u.Data, curr)
|
||||
assert.Equal(t, prev.Signed.Version+1, curr.Signed.Version)
|
||||
assert.Equal(t, u.Version, curr.Signed.Version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSnapshotGeneratePrevCorrupt(t *testing.T) {
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
snapRole := kdb.GetRole(data.CanonicalSnapshotRole)
|
||||
|
||||
for _, id := range snapRole.KeyIDs {
|
||||
k := kdb.GetKey(id)
|
||||
assert.NotNil(t, k)
|
||||
err := store.SetKey("testGUN", data.CanonicalSnapshotRole, k.Algorithm(), k.Public())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
assert.NoError(t, err)
|
||||
root, targets, snapshot, _, err := getUpdates(r, tg, sn, ts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
updates := []storage.MetaUpdate{root, targets}
|
||||
|
||||
// corrupt the JSON structure of prev snapshot
|
||||
snapshot.Data = snapshot.Data[1:]
|
||||
// set the current snapshot in the store manually so we find it when generating
|
||||
// the next version
|
||||
store.UpdateCurrent("testGUN", snapshot)
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
updates, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestValidateSnapshotGenerateNoTargets(t *testing.T) {
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
snapRole := kdb.GetRole(data.CanonicalSnapshotRole)
|
||||
|
||||
for _, id := range snapRole.KeyIDs {
|
||||
k := kdb.GetKey(id)
|
||||
assert.NotNil(t, k)
|
||||
err := store.SetKey("testGUN", data.CanonicalSnapshotRole, k.Algorithm(), k.Public())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
assert.NoError(t, err)
|
||||
root, _, _, _, err := getUpdates(r, tg, sn, ts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
updates := []storage.MetaUpdate{root}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
updates, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestValidateSnapshotGenerateLoadRootTargets(t *testing.T) {
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
snapRole := kdb.GetRole(data.CanonicalSnapshotRole)
|
||||
|
||||
for _, id := range snapRole.KeyIDs {
|
||||
k := kdb.GetKey(id)
|
||||
assert.NotNil(t, k)
|
||||
err := store.SetKey("testGUN", data.CanonicalSnapshotRole, k.Algorithm(), k.Public())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
assert.NoError(t, err)
|
||||
root, targets, _, _, err := getUpdates(r, tg, sn, ts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
updates := []storage.MetaUpdate{}
|
||||
|
||||
store.UpdateCurrent("testGUN", root)
|
||||
store.UpdateCurrent("testGUN", targets)
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
updates, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestPrepRepoLoadRootTargets(t *testing.T) {
|
||||
_, repo, _ := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
assert.NoError(t, err)
|
||||
root, targets, _, _, err := getUpdates(r, tg, sn, ts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
store.UpdateCurrent("testGUN", root)
|
||||
store.UpdateCurrent("testGUN", targets)
|
||||
|
||||
toPrep := tuf.NewRepo(keys.NewDB(), nil)
|
||||
assert.Nil(t, toPrep.Root)
|
||||
assert.Nil(t, toPrep.Targets[data.CanonicalTargetsRole])
|
||||
err = prepRepo("testGUN", toPrep, store)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, toPrep.Root)
|
||||
assert.NotNil(t, toPrep.Targets[data.CanonicalTargetsRole])
|
||||
}
|
||||
|
||||
func TestPrepRepoLoadRootCorrupt(t *testing.T) {
|
||||
_, repo, _ := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
assert.NoError(t, err)
|
||||
root, targets, _, _, err := getUpdates(r, tg, sn, ts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
root.Data = root.Data[:1]
|
||||
store.UpdateCurrent("testGUN", root)
|
||||
store.UpdateCurrent("testGUN", targets)
|
||||
|
||||
toPrep := tuf.NewRepo(keys.NewDB(), nil)
|
||||
err = prepRepo("testGUN", toPrep, store)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestPrepRepoLoadTargetsCorrupt(t *testing.T) {
|
||||
_, repo, _ := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
assert.NoError(t, err)
|
||||
root, targets, _, _, err := getUpdates(r, tg, sn, ts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
targets.Data = targets.Data[:1]
|
||||
store.UpdateCurrent("testGUN", root)
|
||||
store.UpdateCurrent("testGUN", targets)
|
||||
|
||||
toPrep := tuf.NewRepo(keys.NewDB(), nil)
|
||||
err = prepRepo("testGUN", toPrep, store)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestPrepRepoLoadRootMissing(t *testing.T) {
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
toPrep := tuf.NewRepo(nil, nil)
|
||||
err := prepRepo("testGUN", toPrep, store)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// If there is no timestamp key in the store, validation fails. This could
|
||||
// happen if pushing an existing repository from one server to another that
|
||||
// does not have the repo.
|
||||
func TestValidateRootNoTimestampKey(t *testing.T) {
|
||||
_, oldRepo, _ := testutils.EmptyRepo()
|
||||
_, oldRepo, cs := testutils.EmptyRepo()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(oldRepo)
|
||||
assert.NoError(t, err)
|
||||
|
@ -249,7 +461,7 @@ func TestValidateRootNoTimestampKey(t *testing.T) {
|
|||
assert.IsType(t, &storage.ErrNoKey{}, err)
|
||||
|
||||
// do not copy the targets key to the storage, and try to update the root
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadRoot{}, err)
|
||||
|
||||
|
@ -264,7 +476,7 @@ func TestValidateRootNoTimestampKey(t *testing.T) {
|
|||
// repository from one server to another that had already initialized the same
|
||||
// repo.
|
||||
func TestValidateRootInvalidTimestampKey(t *testing.T) {
|
||||
_, oldRepo, _ := testutils.EmptyRepo()
|
||||
_, oldRepo, cs := testutils.EmptyRepo()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(oldRepo)
|
||||
assert.NoError(t, err)
|
||||
|
@ -279,14 +491,14 @@ func TestValidateRootInvalidTimestampKey(t *testing.T) {
|
|||
err = store.SetKey("testGUN", data.CanonicalRootRole, key.Algorithm(), key.Public())
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadRoot{}, err)
|
||||
}
|
||||
|
||||
// If the timestamp role has a threshold > 1, validation fails.
|
||||
func TestValidateRootInvalidTimestampThreshold(t *testing.T) {
|
||||
kdb, oldRepo, _ := testutils.EmptyRepo()
|
||||
kdb, oldRepo, cs := testutils.EmptyRepo()
|
||||
tsRole, ok := oldRepo.Root.Signed.Roles[data.CanonicalTimestampRole]
|
||||
assert.True(t, ok)
|
||||
tsRole.Threshold = 2
|
||||
|
@ -300,7 +512,7 @@ func TestValidateRootInvalidTimestampThreshold(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "timestamp role has invalid threshold")
|
||||
}
|
||||
|
@ -308,7 +520,7 @@ func TestValidateRootInvalidTimestampThreshold(t *testing.T) {
|
|||
// If any role has a threshold < 1, validation fails
|
||||
func TestValidateRootInvalidZeroThreshold(t *testing.T) {
|
||||
for role := range data.ValidRoles {
|
||||
kdb, oldRepo, _ := testutils.EmptyRepo()
|
||||
kdb, oldRepo, cs := testutils.EmptyRepo()
|
||||
tsRole, ok := oldRepo.Root.Signed.Roles[role]
|
||||
assert.True(t, ok)
|
||||
tsRole.Threshold = 0
|
||||
|
@ -322,7 +534,7 @@ func TestValidateRootInvalidZeroThreshold(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "role has invalid threshold")
|
||||
}
|
||||
|
@ -332,7 +544,7 @@ func TestValidateRootInvalidZeroThreshold(t *testing.T) {
|
|||
// These tests remove a role from the Root file and
|
||||
// check for a ErrBadRoot
|
||||
func TestValidateRootRoleMissing(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
delete(repo.Root.Signed.Roles, "root")
|
||||
|
@ -345,13 +557,13 @@ func TestValidateRootRoleMissing(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadRoot{}, err)
|
||||
}
|
||||
|
||||
func TestValidateTargetsRoleMissing(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
delete(repo.Root.Signed.Roles, "targets")
|
||||
|
@ -364,13 +576,13 @@ func TestValidateTargetsRoleMissing(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadRoot{}, err)
|
||||
}
|
||||
|
||||
func TestValidateSnapshotRoleMissing(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
delete(repo.Root.Signed.Roles, "snapshot")
|
||||
|
@ -383,7 +595,7 @@ func TestValidateSnapshotRoleMissing(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadRoot{}, err)
|
||||
}
|
||||
|
@ -392,7 +604,7 @@ func TestValidateSnapshotRoleMissing(t *testing.T) {
|
|||
|
||||
// ### Signature missing negative tests ###
|
||||
func TestValidateRootSigMissing(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
delete(repo.Root.Signed.Roles, "snapshot")
|
||||
|
@ -408,13 +620,13 @@ func TestValidateRootSigMissing(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadRoot{}, err)
|
||||
}
|
||||
|
||||
func TestValidateTargetsSigMissing(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -428,13 +640,13 @@ func TestValidateTargetsSigMissing(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadTargets{}, err)
|
||||
}
|
||||
|
||||
func TestValidateSnapshotSigMissing(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -448,7 +660,7 @@ func TestValidateSnapshotSigMissing(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadSnapshot{}, err)
|
||||
}
|
||||
|
@ -457,7 +669,7 @@ func TestValidateSnapshotSigMissing(t *testing.T) {
|
|||
|
||||
// ### Corrupted metadata negative tests ###
|
||||
func TestValidateRootCorrupt(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -471,13 +683,13 @@ func TestValidateRootCorrupt(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadRoot{}, err)
|
||||
}
|
||||
|
||||
func TestValidateTargetsCorrupt(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -491,13 +703,13 @@ func TestValidateTargetsCorrupt(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadTargets{}, err)
|
||||
}
|
||||
|
||||
func TestValidateSnapshotCorrupt(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -511,7 +723,7 @@ func TestValidateSnapshotCorrupt(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadSnapshot{}, err)
|
||||
}
|
||||
|
@ -520,7 +732,7 @@ func TestValidateSnapshotCorrupt(t *testing.T) {
|
|||
|
||||
// ### Snapshot size mismatch negative tests ###
|
||||
func TestValidateRootModifiedSize(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -538,13 +750,13 @@ func TestValidateRootModifiedSize(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadRoot{}, err)
|
||||
}
|
||||
|
||||
func TestValidateTargetsModifiedSize(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -559,7 +771,7 @@ func TestValidateTargetsModifiedSize(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadSnapshot{}, err)
|
||||
}
|
||||
|
@ -568,7 +780,7 @@ func TestValidateTargetsModifiedSize(t *testing.T) {
|
|||
|
||||
// ### Snapshot hash mismatch negative tests ###
|
||||
func TestValidateRootModifiedHash(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -587,13 +799,13 @@ func TestValidateRootModifiedHash(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadSnapshot{}, err)
|
||||
}
|
||||
|
||||
func TestValidateTargetsModifiedHash(t *testing.T) {
|
||||
kdb, repo, _ := testutils.EmptyRepo()
|
||||
kdb, repo, cs := testutils.EmptyRepo()
|
||||
store := storage.NewMemStorage()
|
||||
|
||||
r, tg, sn, ts, err := testutils.Sign(repo)
|
||||
|
@ -612,7 +824,7 @@ func TestValidateTargetsModifiedHash(t *testing.T) {
|
|||
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
|
||||
|
||||
copyTimestampKey(t, kdb, store, "testGUN")
|
||||
err = validateUpdate("testGUN", updates, store)
|
||||
_, err = validateUpdate(cs, "testGUN", updates, store)
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, ErrBadSnapshot{}, err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
package snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/docker/notary/server/storage"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
)
|
||||
|
||||
// GetOrCreateSnapshotKey either creates a new snapshot key, or returns
|
||||
// the existing one. Only the PublicKey is returned. The private part
|
||||
// is held by the CryptoService.
|
||||
func GetOrCreateSnapshotKey(gun string, store storage.KeyStore, crypto signed.CryptoService, createAlgorithm string) (data.PublicKey, error) {
|
||||
keyAlgorithm, public, err := store.GetKey(gun, data.CanonicalSnapshotRole)
|
||||
if err == nil {
|
||||
return data.NewPublicKey(keyAlgorithm, public), nil
|
||||
}
|
||||
|
||||
if _, ok := err.(*storage.ErrNoKey); ok {
|
||||
key, err := crypto.Create("snapshot", createAlgorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logrus.Debug("Creating new snapshot key for ", gun, ". With algo: ", key.Algorithm())
|
||||
err = store.SetKey(gun, data.CanonicalSnapshotRole, key.Algorithm(), key.Public())
|
||||
if err == nil {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
if _, ok := err.(*storage.ErrKeyExists); ok {
|
||||
keyAlgorithm, public, err = store.GetKey(gun, data.CanonicalSnapshotRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data.NewPublicKey(keyAlgorithm, public), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// GetOrCreateSnapshot either returns the exisiting latest snapshot, or uses
|
||||
// whatever the most recent snapshot is to generate a new one.
|
||||
func GetOrCreateSnapshot(gun string, store storage.MetaStore, cryptoService signed.CryptoService) ([]byte, error) {
|
||||
|
||||
d, err := store.GetCurrent(gun, "snapshot")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sn := &data.SignedSnapshot{}
|
||||
if d != nil {
|
||||
err := json.Unmarshal(d, sn)
|
||||
if err != nil {
|
||||
logrus.Error("Failed to unmarshal existing snapshot")
|
||||
return nil, err
|
||||
}
|
||||
if !snapshotExpired(sn) && !contentExpired(gun, sn, store) {
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
|
||||
sgnd, version, err := createSnapshot(gun, sn, store, cryptoService)
|
||||
if err != nil {
|
||||
logrus.Error("Failed to create a new snapshot")
|
||||
return nil, err
|
||||
}
|
||||
out, err := json.Marshal(sgnd)
|
||||
if err != nil {
|
||||
logrus.Error("Failed to marshal new snapshot")
|
||||
return nil, err
|
||||
}
|
||||
err = store.UpdateCurrent(gun, storage.MetaUpdate{Role: "snapshot", Version: version, Data: out})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// snapshotExpired simply checks if the snapshot is past its expiry time
|
||||
func snapshotExpired(sn *data.SignedSnapshot) bool {
|
||||
return signed.IsExpired(sn.Signed.Expires)
|
||||
}
|
||||
|
||||
// contentExpired checks to see if any of the roles already in the snapshot
|
||||
// have been updated. It will update any roles that have changed as it goes
|
||||
// so that we don't have to run through all this again a second time.
|
||||
func contentExpired(gun string, sn *data.SignedSnapshot, store storage.MetaStore) bool {
|
||||
expired := false
|
||||
updatedMeta := make(data.Files)
|
||||
for role, meta := range sn.Signed.Meta {
|
||||
curr, err := store.GetCurrent(gun, role)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
roleExp, newHash := roleExpired(curr, meta)
|
||||
if roleExp {
|
||||
updatedMeta[role] = data.FileMeta{
|
||||
Length: int64(len(curr)),
|
||||
Hashes: data.Hashes{
|
||||
"sha256": newHash,
|
||||
},
|
||||
}
|
||||
}
|
||||
expired = expired || roleExp
|
||||
}
|
||||
if expired {
|
||||
sn.Signed.Meta = updatedMeta
|
||||
}
|
||||
return expired
|
||||
}
|
||||
|
||||
// roleExpired checks if the content for a specific role differs from
|
||||
// the snapshot
|
||||
func roleExpired(roleData []byte, meta data.FileMeta) (bool, []byte) {
|
||||
currMeta, err := data.NewFileMeta(bytes.NewReader(roleData), "sha256")
|
||||
if err != nil {
|
||||
// if we can't generate FileMeta from the current roleData, we should
|
||||
// continue to serve the old role if it isn't time expired
|
||||
// because we won't be able to generate a new one.
|
||||
return false, nil
|
||||
}
|
||||
hash := currMeta.Hashes["sha256"]
|
||||
return !bytes.Equal(hash, meta.Hashes["sha256"]), hash
|
||||
}
|
||||
|
||||
// createSnapshot uses an existing snapshot to create a new one.
|
||||
// Important things to be aware of:
|
||||
// - It requires that a snapshot already exists. We create snapshots
|
||||
// on upload so there should always be an existing snapshot if this
|
||||
// gets called.
|
||||
// - It doesn't update what roles are present in the snapshot, as those
|
||||
// were validated during upload. We also updated the hashes of the
|
||||
// already present roles as part of our checks on whether we could
|
||||
// serve the previous version of the snapshot
|
||||
func createSnapshot(gun string, sn *data.SignedSnapshot, store storage.MetaStore, cryptoService signed.CryptoService) (*data.Signed, int, error) {
|
||||
algorithm, public, err := store.GetKey(gun, data.CanonicalSnapshotRole)
|
||||
if err != nil {
|
||||
// owner of gun must have generated a snapshot key otherwise
|
||||
// we won't proceed with generating everything.
|
||||
return nil, 0, err
|
||||
}
|
||||
key := data.NewPublicKey(algorithm, public)
|
||||
|
||||
// update version and expiry
|
||||
sn.Signed.Version = sn.Signed.Version + 1
|
||||
sn.Signed.Expires = data.DefaultExpires(data.CanonicalSnapshotRole)
|
||||
|
||||
out, err := sn.ToSigned()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
err = signed.Sign(cryptoService, out, key)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return out, sn.Signed.Version, nil
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
package snapshot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/docker/notary/server/storage"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
)
|
||||
|
||||
func TestSnapshotExpired(t *testing.T) {
|
||||
sn := &data.SignedSnapshot{
|
||||
Signatures: nil,
|
||||
Signed: data.Snapshot{
|
||||
Expires: time.Now().AddDate(-1, 0, 0),
|
||||
},
|
||||
}
|
||||
assert.True(t, snapshotExpired(sn), "Snapshot should have expired")
|
||||
}
|
||||
|
||||
func TestSnapshotNotExpired(t *testing.T) {
|
||||
sn := &data.SignedSnapshot{
|
||||
Signatures: nil,
|
||||
Signed: data.Snapshot{
|
||||
Expires: time.Now().AddDate(1, 0, 0),
|
||||
},
|
||||
}
|
||||
assert.False(t, snapshotExpired(sn), "Snapshot should NOT have expired")
|
||||
}
|
||||
|
||||
func TestGetSnapshotKeyCreate(t *testing.T) {
|
||||
store := storage.NewMemStorage()
|
||||
crypto := signed.NewEd25519()
|
||||
k, err := GetOrCreateSnapshotKey("gun", store, crypto, data.ED25519Key)
|
||||
assert.Nil(t, err, "Expected nil error")
|
||||
assert.NotNil(t, k, "Key should not be nil")
|
||||
|
||||
k2, err := GetOrCreateSnapshotKey("gun", store, crypto, data.ED25519Key)
|
||||
|
||||
assert.Nil(t, err, "Expected nil error")
|
||||
|
||||
// trying to get the same key again should return the same value
|
||||
assert.Equal(t, k, k2, "Did not receive same key when attempting to recreate.")
|
||||
assert.NotNil(t, k2, "Key should not be nil")
|
||||
}
|
||||
|
||||
func TestGetSnapshotKeyExisting(t *testing.T) {
|
||||
store := storage.NewMemStorage()
|
||||
crypto := signed.NewEd25519()
|
||||
key, err := crypto.Create(data.CanonicalSnapshotRole, data.ED25519Key)
|
||||
assert.NoError(t, err)
|
||||
|
||||
store.SetKey("gun", data.CanonicalSnapshotRole, data.ED25519Key, key.Public())
|
||||
|
||||
k, err := GetOrCreateSnapshotKey("gun", store, crypto, data.ED25519Key)
|
||||
assert.Nil(t, err, "Expected nil error")
|
||||
assert.NotNil(t, k, "Key should not be nil")
|
||||
assert.Equal(t, key, k, "Did not receive same key when attempting to recreate.")
|
||||
assert.NotNil(t, k, "Key should not be nil")
|
||||
|
||||
k2, err := GetOrCreateSnapshotKey("gun", store, crypto, data.ED25519Key)
|
||||
|
||||
assert.Nil(t, err, "Expected nil error")
|
||||
|
||||
// trying to get the same key again should return the same value
|
||||
assert.Equal(t, k, k2, "Did not receive same key when attempting to recreate.")
|
||||
assert.NotNil(t, k2, "Key should not be nil")
|
||||
}
|
||||
|
||||
type keyStore struct {
|
||||
getCalled bool
|
||||
k data.PublicKey
|
||||
}
|
||||
|
||||
func (ks *keyStore) GetKey(gun, role string) (string, []byte, error) {
|
||||
defer func() { ks.getCalled = true }()
|
||||
if ks.getCalled {
|
||||
return ks.k.Algorithm(), ks.k.Public(), nil
|
||||
}
|
||||
return "", nil, &storage.ErrNoKey{}
|
||||
}
|
||||
|
||||
func (ks keyStore) SetKey(gun, role, algorithm string, public []byte) error {
|
||||
return &storage.ErrKeyExists{}
|
||||
}
|
||||
|
||||
// Tests the race condition where the server is being asked to generate a new key
|
||||
// by 2 parallel requests and the second insert to be executed by the DB fails
|
||||
// due to duplicate key (gun + role). It should then return the key added by the
|
||||
// first insert.
|
||||
func TestGetSnapshotKeyExistsOnSet(t *testing.T) {
|
||||
crypto := signed.NewEd25519()
|
||||
key, err := crypto.Create(data.CanonicalSnapshotRole, data.ED25519Key)
|
||||
assert.NoError(t, err)
|
||||
store := &keyStore{k: key}
|
||||
|
||||
k, err := GetOrCreateSnapshotKey("gun", store, crypto, data.ED25519Key)
|
||||
assert.Nil(t, err, "Expected nil error")
|
||||
assert.NotNil(t, k, "Key should not be nil")
|
||||
assert.Equal(t, key, k, "Did not receive same key when attempting to recreate.")
|
||||
assert.NotNil(t, k, "Key should not be nil")
|
||||
|
||||
k2, err := GetOrCreateSnapshotKey("gun", store, crypto, data.ED25519Key)
|
||||
|
||||
assert.Nil(t, err, "Expected nil error")
|
||||
|
||||
// trying to get the same key again should return the same value
|
||||
assert.Equal(t, k, k2, "Did not receive same key when attempting to recreate.")
|
||||
assert.NotNil(t, k2, "Key should not be nil")
|
||||
}
|
||||
|
||||
func TestRoleExpired(t *testing.T) {
|
||||
meta := data.FileMeta{
|
||||
Hashes: data.Hashes{
|
||||
"sha256": []byte{1},
|
||||
},
|
||||
}
|
||||
newData := []byte{2}
|
||||
res, _ := roleExpired(newData, meta)
|
||||
assert.True(t, res)
|
||||
}
|
||||
|
||||
func TestRoleNotExpired(t *testing.T) {
|
||||
newData := []byte{2}
|
||||
currMeta, err := data.NewFileMeta(bytes.NewReader(newData), "sha256")
|
||||
assert.NoError(t, err)
|
||||
|
||||
meta := data.FileMeta{
|
||||
Hashes: data.Hashes{
|
||||
"sha256": currMeta.Hashes["sha256"],
|
||||
},
|
||||
}
|
||||
|
||||
res, _ := roleExpired(newData, meta)
|
||||
assert.False(t, res)
|
||||
}
|
||||
|
||||
func TestGetSnapshotNotExists(t *testing.T) {
|
||||
store := storage.NewMemStorage()
|
||||
crypto := signed.NewEd25519()
|
||||
|
||||
_, err := GetOrCreateSnapshot("gun", store, crypto)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetSnapshotCurrValid(t *testing.T) {
|
||||
store := storage.NewMemStorage()
|
||||
crypto := signed.NewEd25519()
|
||||
|
||||
_, err := GetOrCreateSnapshotKey("gun", store, crypto, data.ED25519Key)
|
||||
|
||||
newData := []byte{2}
|
||||
currMeta, err := data.NewFileMeta(bytes.NewReader(newData), "sha256")
|
||||
assert.NoError(t, err)
|
||||
|
||||
snapshot := &data.SignedSnapshot{
|
||||
Signed: data.Snapshot{
|
||||
Expires: data.DefaultExpires(data.CanonicalSnapshotRole),
|
||||
Meta: data.Files{
|
||||
data.CanonicalRootRole: currMeta,
|
||||
},
|
||||
},
|
||||
}
|
||||
snapJSON, _ := json.Marshal(snapshot)
|
||||
|
||||
// test when db is missing the role data
|
||||
store.UpdateCurrent("gun", storage.MetaUpdate{Role: "snapshot", Version: 0, Data: snapJSON})
|
||||
_, err = GetOrCreateSnapshot("gun", store, crypto)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// test when db has the role data
|
||||
store.UpdateCurrent("gun", storage.MetaUpdate{Role: "root", Version: 0, Data: newData})
|
||||
_, err = GetOrCreateSnapshot("gun", store, crypto)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// test when db role data is expired
|
||||
store.UpdateCurrent("gun", storage.MetaUpdate{Role: "root", Version: 1, Data: []byte{3}})
|
||||
_, err = GetOrCreateSnapshot("gun", store, crypto)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetSnapshotCurrExpired(t *testing.T) {
|
||||
store := storage.NewMemStorage()
|
||||
crypto := signed.NewEd25519()
|
||||
|
||||
_, err := GetOrCreateSnapshotKey("gun", store, crypto, data.ED25519Key)
|
||||
|
||||
snapshot := &data.SignedSnapshot{}
|
||||
snapJSON, _ := json.Marshal(snapshot)
|
||||
|
||||
store.UpdateCurrent("gun", storage.MetaUpdate{Role: "snapshot", Version: 0, Data: snapJSON})
|
||||
_, err = GetOrCreateSnapshot("gun", store, crypto)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetSnapshotCurrCorrupt(t *testing.T) {
|
||||
store := storage.NewMemStorage()
|
||||
crypto := signed.NewEd25519()
|
||||
|
||||
_, err := GetOrCreateSnapshotKey("gun", store, crypto, data.ED25519Key)
|
||||
|
||||
snapshot := &data.SignedSnapshot{}
|
||||
snapJSON, _ := json.Marshal(snapshot)
|
||||
|
||||
store.UpdateCurrent("gun", storage.MetaUpdate{Role: "snapshot", Version: 0, Data: snapJSON[1:]})
|
||||
_, err = GetOrCreateSnapshot("gun", store, crypto)
|
||||
assert.Error(t, err)
|
||||
}
|
|
@ -1,5 +1,16 @@
|
|||
package storage
|
||||
|
||||
// KeyStore provides a minimal interface for managing key persistence
|
||||
type KeyStore interface {
|
||||
// GetKey returns the algorithm and public key for the given GUN and role.
|
||||
// If the GUN+role don't exist, returns an error.
|
||||
GetKey(gun, role string) (algorithm string, public []byte, err error)
|
||||
|
||||
// SetKey sets the algorithm and public key for the given GUN and role if
|
||||
// it doesn't already exist. Otherwise an error is returned.
|
||||
SetKey(gun, role, algorithm string, public []byte) error
|
||||
}
|
||||
|
||||
// MetaStore holds the methods that are used for a Metadata Store
|
||||
type MetaStore interface {
|
||||
// UpdateCurrent adds new metadata version for the given GUN if and only
|
||||
|
@ -22,11 +33,5 @@ type MetaStore interface {
|
|||
// error if no metadata exists for the given GUN.
|
||||
Delete(gun string) error
|
||||
|
||||
// GetKey returns the algorithm and public key for the given GUN and role.
|
||||
// If the GUN+role don't exist, returns an error.
|
||||
GetKey(gun, role string) (algorithm string, public []byte, err error)
|
||||
|
||||
// SetKey sets the algorithm and public key for the given GUN and role if
|
||||
// it doesn't already exist. Otherwise an error is returned.
|
||||
SetKey(gun, role, algorithm string, public []byte) error
|
||||
KeyStore
|
||||
}
|
||||
|
|
|
@ -16,14 +16,14 @@ import (
|
|||
// found. It attempts to handle the race condition that may occur if 2 servers try to
|
||||
// create the key at the same time by simply querying the store a second time if it
|
||||
// receives a conflict when writing.
|
||||
func GetOrCreateTimestampKey(gun string, store storage.MetaStore, crypto signed.CryptoService, fallBackAlgorithm string) (data.PublicKey, error) {
|
||||
func GetOrCreateTimestampKey(gun string, store storage.MetaStore, crypto signed.CryptoService, createAlgorithm string) (data.PublicKey, error) {
|
||||
keyAlgorithm, public, err := store.GetKey(gun, data.CanonicalTimestampRole)
|
||||
if err == nil {
|
||||
return data.NewPublicKey(keyAlgorithm, public), nil
|
||||
}
|
||||
|
||||
if _, ok := err.(*storage.ErrNoKey); ok {
|
||||
key, err := crypto.Create("timestamp", fallBackAlgorithm)
|
||||
key, err := crypto.Create("timestamp", createAlgorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -20,7 +20,10 @@ func TestTimestampExpired(t *testing.T) {
|
|||
},
|
||||
}
|
||||
assert.True(t, timestampExpired(ts), "Timestamp should have expired")
|
||||
ts = &data.SignedTimestamp{
|
||||
}
|
||||
|
||||
func TestTimestampNotExpired(t *testing.T) {
|
||||
ts := &data.SignedTimestamp{
|
||||
Signatures: nil,
|
||||
Signed: data.Timestamp{
|
||||
Expires: time.Now().AddDate(1, 0, 0),
|
||||
|
@ -84,7 +87,7 @@ func TestGetTimestampNewSnapshot(t *testing.T) {
|
|||
store.UpdateCurrent("gun", storage.MetaUpdate{Role: "snapshot", Version: 1, Data: snapJSON})
|
||||
|
||||
ts2, err := GetOrCreateTimestamp("gun", store, crypto)
|
||||
assert.Nil(t, err, "GetTimestamp errored")
|
||||
assert.NoError(t, err, "GetTimestamp errored")
|
||||
|
||||
assert.NotEqual(t, ts1, ts2, "Timestamp was not regenerated when snapshot changed")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue