Move validation errors to tuf, since that is the expected server interface.

Also make the validation errors serializable as JSON.

Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
Ying Li 2015-12-09 14:01:46 -08:00
parent 4208945fc1
commit 3aa13e6645
5 changed files with 242 additions and 84 deletions

View File

@ -1,56 +0,0 @@
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

@ -15,6 +15,7 @@ import (
"github.com/docker/notary/tuf/keys"
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/utils"
"github.com/docker/notary/tuf/validation"
)
// validateUpload checks that the updates being pushed
@ -49,26 +50,26 @@ func validateUpdate(cs signed.CryptoService, gun string, updates []storage.MetaU
// against a previous root
if root, err = validateRoot(gun, oldRootJSON, rootUpdate.Data, store); err != nil {
logrus.Error("ErrBadRoot: ", err.Error())
return nil, ErrBadRoot{msg: err.Error()}
return nil, validation.ErrBadRoot{Msg: err.Error()}
}
// setting root will update keys db
if err = repo.SetRoot(root); err != nil {
logrus.Error("ErrValidation: ", err.Error())
return nil, ErrValidation{msg: err.Error()}
return nil, validation.ErrValidation{Msg: err.Error()}
}
logrus.Debug("Successfully validated root")
} else {
if oldRootJSON == nil {
return nil, ErrValidation{msg: "no pre-existing root and no root provided in update."}
return nil, validation.ErrValidation{Msg: "no pre-existing root and no root provided in update."}
}
parsedOldRoot := &data.SignedRoot{}
if err := json.Unmarshal(oldRootJSON, parsedOldRoot); err != nil {
return nil, ErrValidation{msg: "pre-existing root is corrupted and no root provided in update."}
return nil, validation.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 nil, ErrValidation{msg: err.Error()}
return nil, validation.ErrValidation{Msg: err.Error()}
}
}
@ -77,7 +78,7 @@ func validateUpdate(cs signed.CryptoService, gun string, updates []storage.MetaU
if _, ok := roles[targetsRole]; ok {
if t, err = validateTargets(targetsRole, roles, kdb); err != nil {
logrus.Error("ErrBadTargets: ", err.Error())
return nil, ErrBadTargets{msg: err.Error()}
return nil, validation.ErrBadTargets{Msg: err.Error()}
}
repo.SetTargets(targetsRole, t)
}
@ -100,7 +101,7 @@ func validateUpdate(cs signed.CryptoService, gun string, updates []storage.MetaU
if err := validateSnapshot(snapshotRole, oldSnap, roles[snapshotRole], roles, kdb); err != nil {
logrus.Error("ErrBadSnapshot: ", err.Error())
return nil, ErrBadSnapshot{msg: err.Error()}
return nil, validation.ErrBadSnapshot{Msg: err.Error()}
}
logrus.Debug("Successfully validated snapshot")
} else {
@ -154,12 +155,12 @@ func prepRepo(gun string, repo *tuf.Repo, store storage.MetaStore) error {
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"}
return nil, validation.ErrBadRoot{Msg: "root did not include snapshot role"}
}
algo, keyBytes, err := store.GetKey(gun, data.CanonicalSnapshotRole)
if role == nil {
return nil, ErrBadRoot{msg: "root did not include snapshot key"}
return nil, validation.ErrBadRoot{Msg: "root did not include snapshot key"}
}
foundK := data.NewPublicKey(algo, keyBytes)
@ -171,7 +172,7 @@ func generateSnapshot(gun string, kdb *keys.KeyDB, repo *tuf.Repo, store storage
}
}
if !validKey {
return nil, ErrBadHierarchy{msg: "no snapshot was included in update and server does not hold current snapshot key for repository"}
return nil, validation.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)

View File

@ -12,6 +12,7 @@ import (
"github.com/docker/notary/tuf/keys"
"github.com/docker/notary/tuf/signed"
"github.com/docker/notary/tuf/testutils"
"github.com/docker/notary/tuf/validation"
"github.com/stretchr/testify/assert"
"github.com/docker/notary/server/storage"
@ -211,7 +212,7 @@ func TestValidateNoRoot(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrValidation{}, err)
assert.IsType(t, validation.ErrValidation{}, err)
}
func TestValidateSnapshotMissing(t *testing.T) {
@ -228,7 +229,7 @@ func TestValidateSnapshotMissing(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadHierarchy{}, err)
assert.IsType(t, validation.ErrBadHierarchy{}, err)
}
func TestValidateSnapshotGenerateNoPrev(t *testing.T) {
@ -463,7 +464,7 @@ func TestValidateRootNoTimestampKey(t *testing.T) {
// do not copy the targets key to the storage, and try to update the root
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadRoot{}, err)
assert.IsType(t, validation.ErrBadRoot{}, err)
// there should still be no timestamp keys - one should not have been
// created
@ -493,7 +494,7 @@ func TestValidateRootInvalidTimestampKey(t *testing.T) {
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadRoot{}, err)
assert.IsType(t, validation.ErrBadRoot{}, err)
}
// If the timestamp role has a threshold > 1, validation fails.
@ -542,7 +543,7 @@ func TestValidateRootInvalidZeroThreshold(t *testing.T) {
// ### Role missing negative tests ###
// These tests remove a role from the Root file and
// check for a ErrBadRoot
// check for a validation.ErrBadRoot
func TestValidateRootRoleMissing(t *testing.T) {
kdb, repo, cs := testutils.EmptyRepo()
store := storage.NewMemStorage()
@ -559,7 +560,7 @@ func TestValidateRootRoleMissing(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadRoot{}, err)
assert.IsType(t, validation.ErrBadRoot{}, err)
}
func TestValidateTargetsRoleMissing(t *testing.T) {
@ -578,7 +579,7 @@ func TestValidateTargetsRoleMissing(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadRoot{}, err)
assert.IsType(t, validation.ErrBadRoot{}, err)
}
func TestValidateSnapshotRoleMissing(t *testing.T) {
@ -597,7 +598,7 @@ func TestValidateSnapshotRoleMissing(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadRoot{}, err)
assert.IsType(t, validation.ErrBadRoot{}, err)
}
// ### End role missing negative tests ###
@ -622,7 +623,7 @@ func TestValidateRootSigMissing(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadRoot{}, err)
assert.IsType(t, validation.ErrBadRoot{}, err)
}
func TestValidateTargetsSigMissing(t *testing.T) {
@ -642,7 +643,7 @@ func TestValidateTargetsSigMissing(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadTargets{}, err)
assert.IsType(t, validation.ErrBadTargets{}, err)
}
func TestValidateSnapshotSigMissing(t *testing.T) {
@ -662,7 +663,7 @@ func TestValidateSnapshotSigMissing(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadSnapshot{}, err)
assert.IsType(t, validation.ErrBadSnapshot{}, err)
}
// ### End signature missing negative tests ###
@ -685,7 +686,7 @@ func TestValidateRootCorrupt(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadRoot{}, err)
assert.IsType(t, validation.ErrBadRoot{}, err)
}
func TestValidateTargetsCorrupt(t *testing.T) {
@ -705,7 +706,7 @@ func TestValidateTargetsCorrupt(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadTargets{}, err)
assert.IsType(t, validation.ErrBadTargets{}, err)
}
func TestValidateSnapshotCorrupt(t *testing.T) {
@ -725,7 +726,7 @@ func TestValidateSnapshotCorrupt(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadSnapshot{}, err)
assert.IsType(t, validation.ErrBadSnapshot{}, err)
}
// ### End corrupted metadata negative tests ###
@ -752,7 +753,7 @@ func TestValidateRootModifiedSize(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadRoot{}, err)
assert.IsType(t, validation.ErrBadRoot{}, err)
}
func TestValidateTargetsModifiedSize(t *testing.T) {
@ -773,7 +774,7 @@ func TestValidateTargetsModifiedSize(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadSnapshot{}, err)
assert.IsType(t, validation.ErrBadSnapshot{}, err)
}
// ### End snapshot size mismatch negative tests ###
@ -801,7 +802,7 @@ func TestValidateRootModifiedHash(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadSnapshot{}, err)
assert.IsType(t, validation.ErrBadSnapshot{}, err)
}
func TestValidateTargetsModifiedHash(t *testing.T) {
@ -826,7 +827,7 @@ func TestValidateTargetsModifiedHash(t *testing.T) {
copyTimestampKey(t, kdb, store, "testGUN")
_, err = validateUpdate(cs, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, ErrBadSnapshot{}, err)
assert.IsType(t, validation.ErrBadSnapshot{}, err)
}
// ### End snapshot hash mismatch negative tests ###

126
tuf/validation/errors.go Normal file
View File

@ -0,0 +1,126 @@
package validation
import (
"encoding/json"
"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 missing metadata. Currently: a missing snapshot
// at this current time. When delegations are implemented it will also
// represent a missing delegation parent
type ErrBadHierarchy struct {
Missing string
Msg string
}
func (err ErrBadHierarchy) Error() string {
return fmt.Sprintf("Metadata hierarchy is incomplete: %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 metadata 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 metadata 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 metadata is invalid: %s", err.Msg)
}
// END VALIDATION ERRORS
// SerializableError is a struct that can be used to serialize an error as JSON
type SerializableError struct {
Name string
Error error
}
// UnmarshalJSON attempts to unmarshal the error into the right type
func (s *SerializableError) UnmarshalJSON(text []byte) (err error) {
var x struct{ Name string }
err = json.Unmarshal(text, &x)
if err != nil {
return
}
var theError error
switch x.Name {
case "ErrValidation":
var e struct{ Error ErrValidation }
err = json.Unmarshal(text, &e)
theError = e.Error
case "ErrBadHierarchy":
var e struct{ Error ErrBadHierarchy }
err = json.Unmarshal(text, &e)
theError = e.Error
case "ErrBadRoot":
var e struct{ Error ErrBadRoot }
err = json.Unmarshal(text, &e)
theError = e.Error
case "ErrBadTargets":
var e struct{ Error ErrBadTargets }
err = json.Unmarshal(text, &e)
theError = e.Error
case "ErrBadSnapshot":
var e struct{ Error ErrBadSnapshot }
err = json.Unmarshal(text, &e)
theError = e.Error
default:
err = fmt.Errorf("do not know how to unmarshall %s", x.Name)
return
}
if err != nil {
return
}
s.Name = x.Name
s.Error = theError
return nil
}
// NewSerializableError serializes one of the above errors into JSON
func NewSerializableError(err error) (*SerializableError, error) {
// make sure it's one of our errors
var name string
switch err.(type) {
case ErrValidation:
name = "ErrValidation"
case ErrBadHierarchy:
name = "ErrBadHierarchy"
case ErrBadRoot:
name = "ErrBadRoot"
case ErrBadTargets:
name = "ErrBadTargets"
case ErrBadSnapshot:
name = "ErrBadSnapshot"
default:
return nil, fmt.Errorf("does not support serializing non-validation errors")
}
return &SerializableError{Name: name, Error: err}, nil
}

View File

@ -0,0 +1,86 @@
package validation
import (
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
// NewSerializableError errors if some random error is not returned
func TestNewSerializableErrorNonValidationError(t *testing.T) {
_, err := NewSerializableError(fmt.Errorf("not validation error"))
assert.Error(t, err)
}
// NewSerializableError succeeds if a validation error is passed to it
func TestNewSerializableErrorValidationError(t *testing.T) {
vError := ErrValidation{"validation error"}
s, err := NewSerializableError(vError)
assert.NoError(t, err)
assert.Equal(t, "ErrValidation", s.Name)
assert.Equal(t, vError, s.Error)
}
// We can unmarshal a marshalled SerializableError
func TestUnmarshalSerialiableErrorSuccessfully(t *testing.T) {
origS, err := NewSerializableError(ErrBadHierarchy{Missing: "root", Msg: "badness"})
assert.NoError(t, err)
b, err := json.Marshal(origS)
assert.NoError(t, err)
jsonBytes := [][]byte{
b,
[]byte(`{"Name":"ErrBadHierarchy","Error":{"Missing":"root","Msg":"badness"}}`),
}
for _, toUnmarshal := range jsonBytes {
var newS SerializableError
err = json.Unmarshal(toUnmarshal, &newS)
assert.NoError(t, err)
assert.Equal(t, "ErrBadHierarchy", newS.Name)
e, ok := newS.Error.(ErrBadHierarchy)
assert.True(t, ok)
assert.Equal(t, "root", e.Missing)
assert.Equal(t, "badness", e.Msg)
}
}
// If the name is unrecognized, unmarshalling will error
func TestUnmarshalUnknownErrorName(t *testing.T) {
origS := SerializableError{Name: "boop", Error: ErrBadRoot{"bad"}}
b, err := json.Marshal(origS)
assert.NoError(t, err)
var newS SerializableError
err = json.Unmarshal(b, &newS)
assert.Error(t, err)
}
// If the error is unmarshallable, unmarshalling will error even if the name
// is valid
func TestUnmarshalInvalidError(t *testing.T) {
var newS SerializableError
err := json.Unmarshal([]byte(`{"Name": "ErrBadRoot", "Error": "meh"}`), &newS)
assert.Error(t, err)
}
// If there is no name, unmarshalling will error even if the error is valid
func TestUnmarshalNoName(t *testing.T) {
origS := SerializableError{Error: ErrBadRoot{"bad"}}
b, err := json.Marshal(origS)
assert.NoError(t, err)
var newS SerializableError
err = json.Unmarshal(b, &newS)
assert.Error(t, err)
}
func TestUnmarshalInvalidJSON(t *testing.T) {
var newS SerializableError
err := json.Unmarshal([]byte("{"), &newS)
assert.Error(t, err)
}