generate snapshots server side

Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
This commit is contained in:
David Lawrence 2015-12-02 15:50:42 -08:00
parent d02f6f2686
commit cae5940c70
6 changed files with 650 additions and 152 deletions

View File

@ -40,6 +40,12 @@ func AtomicUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Req
if !ok {
return errors.ErrNoStorage.WithDetail(nil)
}
cryptoServiceVal := ctx.Value("cryptoService")
cryptoService, ok := cryptoServiceVal.(signed.CryptoService)
if !ok {
return errors.ErrNoCryptoService.WithDetail(nil)
}
vars := mux.Vars(r)
gun := vars["imageName"]
reader, err := r.MultipartReader()
@ -73,7 +79,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)

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,148 @@ 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
//
// TODO: Assumptions have been made about what is loaded into the repo.
// we need to ensure at a minimum the root and targets are loaded
// in.
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)
foundK := data.NewPublicKey(algo, keyBytes)
validKey := false
for _, id := range role.KeyIDs {
if id == foundK.ID() {
validKey = true
break
}
}
logrus.Debug("Successfully validated snapshot")
return nil
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)
}
} else {
root, err := repo.Root.ToSigned()
if err != nil {
return nil, fmt.Errorf("could not create snapshot: %v", err)
}
targets, err := repo.Targets[data.CanonicalTargetsRole].ToSigned()
if err != nil {
return nil, fmt.Errorf("could not create snapshot: %v", err)
}
sn, err = data.NewSnapshot(root, targets)
if err != nil {
return nil, fmt.Errorf("could not create snapshot: %v", err)
}
}
err = repo.SetSnapshot(sn)
if err != nil {
return nil, fmt.Errorf("could not create 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 +302,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 TestPreRepoLoadRootTargets(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 TestPreRepoLoadRootCorrupt(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 TestPreRepoLoadTargetsCorrupt(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 TestPreRepoLoadRootMissing(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)
}

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

@ -0,0 +1,175 @@
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.MetaStore, 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 {
if _, ok := err.(*storage.ErrNotFound); !ok {
logrus.Error("error retrieving timestamp: ", err.Error())
return nil, err
}
logrus.Debug("No snapshot found, will proceed to create first snapshot")
}
sn := &data.SignedSnapshot{}
if d != nil {
err := json.Unmarshal(d, sn)
if err != nil {
logrus.Error("Failed to unmarshal existing timestamp")
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 timestamp")
return nil, err
}
out, err := json.Marshal(sgnd)
if err != nil {
logrus.Error("Failed to marshal new timestamp")
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 := range sn.Signed.Meta {
curr, err := store.GetCurrent(gun, role)
if err != nil {
return false
}
roleExpired, newHash := roleExpired(sn, role, curr)
if roleExpired {
updatedMeta[role] = data.FileMeta{
Length: int64(len(curr)),
Hashes: data.Hashes{
"sha256": newHash,
},
}
}
expired = expired || roleExpired
}
if expired {
sn.Signed.Meta = updatedMeta
}
return expired
}
// roleExpired checks if the content for a specific role differs from
// the snapshot
func roleExpired(sn *data.SignedSnapshot, role string, roleData []byte) (bool, []byte) {
meta, 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 := meta.Hashes["sha256"]
// if the role doesn't exist in the snapshot, it's out of date
// and needs to be updated. This should never actually happen
// with our existing strategy as we're iterating only roles
// that exist in the snapshot already. I'm still putting the
// check in to future proof against myself.
if _, ok := sn.Signed.Meta[role]; !ok {
return true, hash
}
return !bytes.Equal(hash, sn.Signed.Meta[role].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

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