Merge pull request #332 from endophage/server_snapshot

Server snapshot
This commit is contained in:
David Lawrence 2015-12-07 14:56:07 -08:00
commit dd69872bb6
10 changed files with 1055 additions and 170 deletions

View File

@ -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 {

View File

@ -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)
}

56
server/handlers/errors.go Normal file
View File

@ -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

View File

@ -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) {

View File

@ -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)
}

161
server/snapshot/snapshot.go Normal file
View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
}