mirror of https://github.com/docker/docs.git
generate snapshots server side
Signed-off-by: David Lawrence <david.lawrence@docker.com> (github: endophage)
This commit is contained in:
parent
d02f6f2686
commit
cae5940c70
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// VALIDATION ERRORS:
|
||||
|
||||
// ErrValidation represents a general validation error
|
||||
type ErrValidation struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrValidation) Error() string {
|
||||
return fmt.Sprintf("An error occurred during validation: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadHierarchy represents a missing snapshot at this current time.
|
||||
// When delegations are implemented it will also represent a missing
|
||||
// delegation parent
|
||||
type ErrBadHierarchy struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadHierarchy) Error() string {
|
||||
return fmt.Sprintf("Hierarchy of updates in incorrect: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadRoot represents a failure validating the root
|
||||
type ErrBadRoot struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadRoot) Error() string {
|
||||
return fmt.Sprintf("The root being updated is invalid: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadTargets represents a failure to validate a targets (incl delegations)
|
||||
type ErrBadTargets struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadTargets) Error() string {
|
||||
return fmt.Sprintf("The targets being updated is invalid: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadSnapshot represents a failure to validate the snapshot
|
||||
type ErrBadSnapshot struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadSnapshot) Error() string {
|
||||
return fmt.Sprintf("The snapshot being updated is invalid: %s", err.msg)
|
||||
}
|
||||
|
||||
// END VALIDATION ERRORS
|
|
@ -7,68 +7,25 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/docker/notary/server/storage"
|
||||
"github.com/docker/notary/tuf"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary/server/storage"
|
||||
"github.com/docker/notary/tuf/keys"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
"github.com/docker/notary/tuf/utils"
|
||||
)
|
||||
|
||||
// ErrValidation represents a general validation error
|
||||
type ErrValidation struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrValidation) Error() string {
|
||||
return fmt.Sprintf("An error occurred during validation: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadHierarchy represents a missing snapshot at this current time.
|
||||
// When delegations are implemented it will also represent a missing
|
||||
// delegation parent
|
||||
type ErrBadHierarchy struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadHierarchy) Error() string {
|
||||
return fmt.Sprintf("Hierarchy of updates in incorrect: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadRoot represents a failure validating the root
|
||||
type ErrBadRoot struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadRoot) Error() string {
|
||||
return fmt.Sprintf("The root being updated is invalid: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadTargets represents a failure to validate a targets (incl delegations)
|
||||
type ErrBadTargets struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadTargets) Error() string {
|
||||
return fmt.Sprintf("The targets being updated is invalid: %s", err.msg)
|
||||
}
|
||||
|
||||
// ErrBadSnapshot represents a failure to validate the snapshot
|
||||
type ErrBadSnapshot struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (err ErrBadSnapshot) Error() string {
|
||||
return fmt.Sprintf("The snapshot being updated is invalid: %s", err.msg)
|
||||
}
|
||||
|
||||
// validateUpload checks that the updates being pushed
|
||||
// are semantically correct and the signatures are correct
|
||||
func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.MetaStore) error {
|
||||
// A list of possibly modified updates are returned if all
|
||||
// validation was successful. This allows the snapshot to be
|
||||
// created and added if snapshotting has been delegated to the
|
||||
// server
|
||||
func validateUpdate(cs signed.CryptoService, gun string, updates []storage.MetaUpdate, store storage.MetaStore) ([]storage.MetaUpdate, error) {
|
||||
kdb := keys.NewDB()
|
||||
repo := tuf.NewRepo(kdb, nil)
|
||||
repo := tuf.NewRepo(kdb, cs)
|
||||
rootRole := data.RoleName(data.CanonicalRootRole)
|
||||
targetsRole := data.RoleName(data.CanonicalTargetsRole)
|
||||
snapshotRole := data.RoleName(data.CanonicalSnapshotRole)
|
||||
|
@ -78,11 +35,6 @@ func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.Meta
|
|||
for _, v := range updates {
|
||||
roles[v.Role] = v
|
||||
}
|
||||
if err := hierarchyOK(roles); err != nil {
|
||||
logrus.Error("ErrBadHierarchy: ", err.Error())
|
||||
return ErrBadHierarchy{msg: err.Error()}
|
||||
}
|
||||
logrus.Debug("Successfully validated hierarchy")
|
||||
|
||||
var root *data.SignedRoot
|
||||
oldRootJSON, err := store.GetCurrent(gun, rootRole)
|
||||
|
@ -90,33 +42,33 @@ func validateUpdate(gun string, updates []storage.MetaUpdate, store storage.Meta
|
|||
// problem with storage. No expectation we can
|
||||
// write if we can't read so bail.
|
||||
logrus.Error("error reading previous root: ", err.Error())
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if rootUpdate, ok := roles[rootRole]; ok {
|
||||
// if root is present, validate its integrity, possibly
|
||||
// against a previous root
|
||||
if root, err = validateRoot(gun, oldRootJSON, rootUpdate.Data, store); err != nil {
|
||||
logrus.Error("ErrBadRoot: ", err.Error())
|
||||
return ErrBadRoot{msg: err.Error()}
|
||||
return nil, ErrBadRoot{msg: err.Error()}
|
||||
}
|
||||
|
||||
// setting root will update keys db
|
||||
if err = repo.SetRoot(root); err != nil {
|
||||
logrus.Error("ErrValidation: ", err.Error())
|
||||
return ErrValidation{msg: err.Error()}
|
||||
return nil, ErrValidation{msg: err.Error()}
|
||||
}
|
||||
logrus.Debug("Successfully validated root")
|
||||
} else {
|
||||
if oldRootJSON == nil {
|
||||
return ErrValidation{msg: "no pre-existing root and no root provided in update."}
|
||||
return nil, ErrValidation{msg: "no pre-existing root and no root provided in update."}
|
||||
}
|
||||
parsedOldRoot := &data.SignedRoot{}
|
||||
if err := json.Unmarshal(oldRootJSON, parsedOldRoot); err != nil {
|
||||
return ErrValidation{msg: "pre-existing root is corrupted and no root provided in update."}
|
||||
return nil, ErrValidation{msg: "pre-existing root is corrupted and no root provided in update."}
|
||||
}
|
||||
if err = repo.SetRoot(parsedOldRoot); err != nil {
|
||||
logrus.Error("ErrValidation: ", err.Error())
|
||||
return ErrValidation{msg: err.Error()}
|
||||
return nil, ErrValidation{msg: err.Error()}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,32 +77,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) {
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue