mirror of https://github.com/docker/docs.git
520 lines
16 KiB
Go
520 lines
16 KiB
Go
package storage
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/docker/notary/tuf/data"
|
|
"github.com/jinzhu/gorm"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// SampleTUF returns a sample TUFFile with the given Version (ID will have
|
|
// to be set independently)
|
|
func SampleTUF(version int) TUFFile {
|
|
return SampleCustomTUF(data.CanonicalRootRole, "testGUN", []byte("1"), version)
|
|
}
|
|
|
|
func SampleCustomTUF(role, gun string, data []byte, version int) TUFFile {
|
|
checksum := sha256.Sum256(data)
|
|
hexChecksum := hex.EncodeToString(checksum[:])
|
|
return TUFFile{
|
|
Gun: gun,
|
|
Role: role,
|
|
Version: version,
|
|
Sha256: hexChecksum,
|
|
Data: data,
|
|
}
|
|
}
|
|
|
|
func SampleUpdate(version int) MetaUpdate {
|
|
return MetaUpdate{
|
|
Role: "root",
|
|
Version: version,
|
|
Data: []byte("1"),
|
|
}
|
|
}
|
|
|
|
// SetUpSQLite creates a sqlite database for testing
|
|
func SetUpSQLite(t *testing.T, dbDir string) (*gorm.DB, *SQLStorage) {
|
|
dbStore, err := NewSQLStorage("sqlite3", dbDir+"test_db")
|
|
require.NoError(t, err)
|
|
|
|
// Create the DB tables
|
|
err = CreateTUFTable(dbStore.DB)
|
|
require.NoError(t, err)
|
|
|
|
err = CreateKeyTable(dbStore.DB)
|
|
require.NoError(t, err)
|
|
|
|
// verify that the tables are empty
|
|
var count int
|
|
for _, model := range [2]interface{}{&TUFFile{}, &Key{}} {
|
|
query := dbStore.DB.Model(model).Count(&count)
|
|
require.NoError(t, query.Error)
|
|
require.Equal(t, 0, count)
|
|
}
|
|
return &dbStore.DB, dbStore
|
|
}
|
|
|
|
// TestSQLUpdateCurrent asserts that UpdateCurrent will add a new TUF file
|
|
// if no previous version existed.
|
|
func TestSQLUpdateCurrentNew(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
gormDB, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
// Adding a new TUF file should succeed
|
|
err = dbStore.UpdateCurrent("testGUN", SampleUpdate(0))
|
|
require.NoError(t, err, "Creating a row in an empty DB failed.")
|
|
|
|
// There should just be one row
|
|
var rows []TUFFile
|
|
query := gormDB.Select("ID, Gun, Role, Version, Sha256, Data").Find(&rows)
|
|
require.NoError(t, query.Error)
|
|
|
|
expected := SampleTUF(0)
|
|
expected.ID = 1
|
|
require.Equal(t, []TUFFile{expected}, rows)
|
|
}
|
|
|
|
// TestSQLUpdateCurrentNewVersion asserts that UpdateCurrent will add a
|
|
// new (higher) version of an existing TUF file
|
|
func TestSQLUpdateCurrentNewVersion(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
gormDB, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
// insert row
|
|
oldVersion := SampleTUF(0)
|
|
query := gormDB.Create(&oldVersion)
|
|
require.NoError(t, query.Error, "Creating a row in an empty DB failed.")
|
|
|
|
// UpdateCurrent with a newer version should succeed
|
|
update := SampleUpdate(2)
|
|
err = dbStore.UpdateCurrent("testGUN", update)
|
|
require.NoError(t, err, "Creating a row in an empty DB failed.")
|
|
|
|
// There should just be one row
|
|
var rows []TUFFile
|
|
query = gormDB.Select("ID, Gun, Role, Version, Sha256, Data").Find(&rows)
|
|
require.NoError(t, query.Error)
|
|
|
|
oldVersion.Model = gorm.Model{ID: 1}
|
|
expected := SampleTUF(2)
|
|
expected.Model = gorm.Model{ID: 2}
|
|
require.Equal(t, []TUFFile{oldVersion, expected}, rows)
|
|
}
|
|
|
|
// TestSQLUpdateCurrentOldVersionError asserts that an error is raised if
|
|
// trying to update to an older version of a TUF file.
|
|
func TestSQLUpdateCurrentOldVersionError(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
gormDB, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
// insert row
|
|
newVersion := SampleTUF(3)
|
|
query := gormDB.Create(&newVersion)
|
|
require.NoError(t, query.Error, "Creating a row in an empty DB failed.")
|
|
|
|
// UpdateCurrent should fail due to the version being lower than the
|
|
// previous row
|
|
err = dbStore.UpdateCurrent("testGUN", SampleUpdate(0))
|
|
require.Error(t, err, "Error should not be nil")
|
|
require.IsType(t, &ErrOldVersion{}, err,
|
|
"Expected ErrOldVersion error type, got: %v", err)
|
|
|
|
// There should just be one row
|
|
var rows []TUFFile
|
|
query = gormDB.Select("ID, Gun, Role, Version, Sha256, Data").Find(&rows)
|
|
require.NoError(t, query.Error)
|
|
|
|
newVersion.Model = gorm.Model{ID: 1}
|
|
require.Equal(t, []TUFFile{newVersion}, rows)
|
|
|
|
dbStore.DB.Close()
|
|
}
|
|
|
|
// TestSQLUpdateMany asserts that inserting multiple updates succeeds if the
|
|
// updates do not conflict with each.
|
|
func TestSQLUpdateMany(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
gormDB, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
err = dbStore.UpdateMany("testGUN", []MetaUpdate{
|
|
SampleUpdate(0),
|
|
{
|
|
Role: "targets",
|
|
Version: 1,
|
|
Data: []byte("2"),
|
|
},
|
|
SampleUpdate(2),
|
|
})
|
|
require.NoError(t, err, "UpdateMany errored unexpectedly: %v", err)
|
|
|
|
gorm1 := SampleTUF(0)
|
|
gorm1.ID = 1
|
|
data := []byte("2")
|
|
checksum := sha256.Sum256(data)
|
|
hexChecksum := hex.EncodeToString(checksum[:])
|
|
gorm2 := TUFFile{
|
|
Model: gorm.Model{ID: 2}, Gun: "testGUN", Role: "targets",
|
|
Version: 1, Sha256: hexChecksum, Data: data}
|
|
gorm3 := SampleTUF(2)
|
|
gorm3.ID = 3
|
|
expected := []TUFFile{gorm1, gorm2, gorm3}
|
|
|
|
var rows []TUFFile
|
|
query := gormDB.Select("ID, Gun, Role, Version, Sha256, Data").Find(&rows)
|
|
require.NoError(t, query.Error)
|
|
require.Equal(t, expected, rows)
|
|
|
|
dbStore.DB.Close()
|
|
}
|
|
|
|
// TestSQLUpdateManyVersionOrder asserts that inserting updates with
|
|
// non-monotonic versions still succeeds.
|
|
func TestSQLUpdateManyVersionOrder(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
gormDB, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
err = dbStore.UpdateMany(
|
|
"testGUN", []MetaUpdate{SampleUpdate(2), SampleUpdate(0)})
|
|
require.NoError(t, err)
|
|
|
|
// the whole transaction should have rolled back, so there should be
|
|
// no entries.
|
|
gorm1 := SampleTUF(2)
|
|
gorm1.ID = 1
|
|
gorm2 := SampleTUF(0)
|
|
gorm2.ID = 2
|
|
|
|
var rows []TUFFile
|
|
query := gormDB.Select("ID, Gun, Role, Version, Sha256, Data").Find(&rows)
|
|
require.NoError(t, query.Error)
|
|
require.Equal(t, []TUFFile{gorm1, gorm2}, rows)
|
|
|
|
dbStore.DB.Close()
|
|
}
|
|
|
|
// TestSQLUpdateManyDuplicateRollback asserts that inserting duplicate
|
|
// updates fails.
|
|
func TestSQLUpdateManyDuplicateRollback(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
gormDB, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
update := SampleUpdate(0)
|
|
err = dbStore.UpdateMany("testGUN", []MetaUpdate{update, update})
|
|
require.Error(
|
|
t, err, "There should be an error updating the same data twice.")
|
|
require.IsType(t, &ErrOldVersion{}, err,
|
|
"UpdateMany returned wrong error type")
|
|
|
|
// the whole transaction should have rolled back, so there should be
|
|
// no entries.
|
|
var count int
|
|
query := gormDB.Model(&TUFFile{}).Count(&count)
|
|
require.NoError(t, query.Error)
|
|
require.Equal(t, 0, count)
|
|
|
|
dbStore.DB.Close()
|
|
}
|
|
|
|
func TestSQLGetCurrent(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
gormDB, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
_, byt, err := dbStore.GetCurrent("testGUN", "root")
|
|
require.Nil(t, byt)
|
|
require.Error(t, err, "There should be an error Getting an empty table")
|
|
require.IsType(t, ErrNotFound{}, err, "Should get a not found error")
|
|
|
|
tuf := SampleTUF(0)
|
|
query := gormDB.Create(&tuf)
|
|
require.NoError(t, query.Error, "Creating a row in an empty DB failed.")
|
|
|
|
cDate, byt, err := dbStore.GetCurrent("testGUN", "root")
|
|
require.NoError(t, err, "There should not be any errors getting.")
|
|
require.Equal(t, []byte("1"), byt, "Returned data was incorrect")
|
|
// the update date was sometime wthin the last minute
|
|
fmt.Println(cDate)
|
|
require.True(t, cDate.After(time.Now().Add(-1*time.Minute)))
|
|
require.True(t, cDate.Before(time.Now().Add(5*time.Second)))
|
|
|
|
dbStore.DB.Close()
|
|
}
|
|
|
|
func TestSQLDelete(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
gormDB, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
tuf := SampleTUF(0)
|
|
query := gormDB.Create(&tuf)
|
|
require.NoError(t, query.Error, "Creating a row in an empty DB failed.")
|
|
|
|
err = dbStore.Delete("testGUN")
|
|
require.NoError(t, err, "There should not be any errors deleting.")
|
|
|
|
// verify deletion
|
|
var count int
|
|
query = gormDB.Model(&TUFFile{}).Count(&count)
|
|
require.NoError(t, query.Error)
|
|
require.Equal(t, 0, count)
|
|
|
|
dbStore.DB.Close()
|
|
}
|
|
|
|
func TestSQLGetKeyNoKey(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
gormDB, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
cipher, public, err := dbStore.GetKey("testGUN", data.CanonicalTimestampRole)
|
|
require.Equal(t, "", cipher)
|
|
require.Nil(t, public)
|
|
require.IsType(t, &ErrNoKey{}, err,
|
|
"Expected ErrNoKey from GetKey")
|
|
|
|
query := gormDB.Create(&Key{
|
|
Gun: "testGUN",
|
|
Role: data.CanonicalTimestampRole,
|
|
Cipher: "testCipher",
|
|
Public: []byte("1"),
|
|
})
|
|
require.NoError(
|
|
t, query.Error, "Inserting timestamp into empty DB should succeed")
|
|
|
|
cipher, public, err = dbStore.GetKey("testGUN", data.CanonicalTimestampRole)
|
|
require.Equal(t, "testCipher", cipher,
|
|
"Returned cipher was incorrect")
|
|
require.Equal(t, []byte("1"), public, "Returned pubkey was incorrect")
|
|
}
|
|
|
|
func TestSQLSetKeyExists(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
gormDB, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
err = dbStore.SetKey("testGUN", data.CanonicalTimestampRole, "testCipher", []byte("1"))
|
|
require.NoError(t, err, "Inserting timestamp into empty DB should succeed")
|
|
|
|
err = dbStore.SetKey("testGUN", data.CanonicalTimestampRole, "testCipher", []byte("1"))
|
|
require.Error(t, err)
|
|
require.IsType(t, &ErrKeyExists{}, err,
|
|
"Expected ErrKeyExists from SetKey")
|
|
|
|
var rows []Key
|
|
query := gormDB.Select("ID, Gun, Cipher, Public").Find(&rows)
|
|
require.NoError(t, query.Error)
|
|
|
|
expected := Key{Gun: "testGUN", Cipher: "testCipher",
|
|
Public: []byte("1")}
|
|
expected.Model = gorm.Model{ID: 1}
|
|
|
|
require.Equal(t, []Key{expected}, rows)
|
|
|
|
dbStore.DB.Close()
|
|
}
|
|
|
|
func TestSQLSetKeyMultipleRoles(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
gormDB, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
err = dbStore.SetKey("testGUN", data.CanonicalTimestampRole, "testCipher", []byte("1"))
|
|
require.NoError(t, err, "Inserting timestamp into empty DB should succeed")
|
|
|
|
err = dbStore.SetKey("testGUN", data.CanonicalSnapshotRole, "testCipher", []byte("1"))
|
|
require.NoError(t, err, "Inserting snapshot key into DB with timestamp key should succeed")
|
|
|
|
var rows []Key
|
|
query := gormDB.Select("ID, Gun, Role, Cipher, Public").Find(&rows)
|
|
require.NoError(t, query.Error)
|
|
|
|
expectedTS := Key{Gun: "testGUN", Role: "timestamp", Cipher: "testCipher",
|
|
Public: []byte("1")}
|
|
expectedTS.Model = gorm.Model{ID: 1}
|
|
|
|
expectedSN := Key{Gun: "testGUN", Role: "snapshot", Cipher: "testCipher",
|
|
Public: []byte("1")}
|
|
expectedSN.Model = gorm.Model{ID: 2}
|
|
|
|
require.Equal(t, []Key{expectedTS, expectedSN}, rows)
|
|
|
|
dbStore.DB.Close()
|
|
}
|
|
|
|
func TestSQLSetKeyMultipleGuns(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
gormDB, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
err = dbStore.SetKey("testGUN", data.CanonicalTimestampRole, "testCipher", []byte("1"))
|
|
require.NoError(t, err, "Inserting timestamp into empty DB should succeed")
|
|
|
|
err = dbStore.SetKey("testAnotherGUN", data.CanonicalTimestampRole, "testCipher", []byte("1"))
|
|
require.NoError(t, err, "Inserting snapshot key into DB with timestamp key should succeed")
|
|
|
|
var rows []Key
|
|
query := gormDB.Select("ID, Gun, Role, Cipher, Public").Find(&rows)
|
|
require.NoError(t, query.Error)
|
|
|
|
expected1 := Key{Gun: "testGUN", Role: "timestamp", Cipher: "testCipher",
|
|
Public: []byte("1")}
|
|
expected1.Model = gorm.Model{ID: 1}
|
|
|
|
expected2 := Key{Gun: "testAnotherGUN", Role: "timestamp", Cipher: "testCipher",
|
|
Public: []byte("1")}
|
|
expected2.Model = gorm.Model{ID: 2}
|
|
|
|
require.Equal(t, []Key{expected1, expected2}, rows)
|
|
|
|
dbStore.DB.Close()
|
|
}
|
|
|
|
func TestSQLSetKeySameRoleGun(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
_, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
err = dbStore.SetKey("testGUN", data.CanonicalTimestampRole, "testCipher", []byte("1"))
|
|
require.NoError(t, err, "Inserting timestamp into empty DB should succeed")
|
|
|
|
err = dbStore.SetKey("testGUN", data.CanonicalTimestampRole, "testCipher", []byte("2"))
|
|
require.Error(t, err)
|
|
require.IsType(t, &ErrKeyExists{}, err,
|
|
"Expected ErrKeyExists from SetKey")
|
|
|
|
dbStore.DB.Close()
|
|
}
|
|
|
|
// TestDBCheckHealthTableMissing asserts that the health check fails if one or
|
|
// both the tables are missing.
|
|
func TestDBCheckHealthTableMissing(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
_, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
dbStore.DropTable(&TUFFile{})
|
|
dbStore.DropTable(&Key{})
|
|
|
|
// No tables, health check fails
|
|
err = dbStore.CheckHealth()
|
|
require.Error(t, err, "Cannot access table:")
|
|
|
|
// only one table existing causes health check to fail
|
|
CreateTUFTable(dbStore.DB)
|
|
err = dbStore.CheckHealth()
|
|
require.Error(t, err, "Cannot access table:")
|
|
dbStore.DropTable(&TUFFile{})
|
|
|
|
CreateKeyTable(dbStore.DB)
|
|
err = dbStore.CheckHealth()
|
|
require.Error(t, err, "Cannot access table:")
|
|
}
|
|
|
|
// TestDBCheckHealthDBCOnnection asserts that if the DB is not connectable, the
|
|
// health check fails.
|
|
func TestDBCheckHealthDBConnectionFail(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
_, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
err = dbStore.Close()
|
|
require.NoError(t, err)
|
|
|
|
err = dbStore.CheckHealth()
|
|
require.Error(t, err, "Cannot access table:")
|
|
}
|
|
|
|
// TestDBCheckHealthSuceeds asserts that if the DB is connectable and both
|
|
// tables exist, the health check succeeds.
|
|
func TestDBCheckHealthSucceeds(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
_, dbStore := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
err = dbStore.CheckHealth()
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestDBGetChecksum(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
_, store := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
ts := data.SignedTimestamp{
|
|
Signatures: make([]data.Signature, 0),
|
|
Signed: data.Timestamp{
|
|
SignedCommon: data.SignedCommon{
|
|
Type: data.TUFTypes[data.CanonicalTimestampRole],
|
|
Version: 1,
|
|
Expires: data.DefaultExpires(data.CanonicalTimestampRole),
|
|
},
|
|
},
|
|
}
|
|
j, err := json.Marshal(&ts)
|
|
require.NoError(t, err)
|
|
update := MetaUpdate{
|
|
Role: data.CanonicalTimestampRole,
|
|
Version: 1,
|
|
Data: j,
|
|
}
|
|
checksumBytes := sha256.Sum256(j)
|
|
checksum := hex.EncodeToString(checksumBytes[:])
|
|
|
|
store.UpdateCurrent("gun", update)
|
|
|
|
// create and add a newer timestamp. We're going to try and get the one
|
|
// created above by checksum
|
|
ts = data.SignedTimestamp{
|
|
Signatures: make([]data.Signature, 0),
|
|
Signed: data.Timestamp{
|
|
SignedCommon: data.SignedCommon{
|
|
Type: data.TUFTypes[data.CanonicalTimestampRole],
|
|
Version: 2,
|
|
Expires: data.DefaultExpires(data.CanonicalTimestampRole),
|
|
},
|
|
},
|
|
}
|
|
newJ, err := json.Marshal(&ts)
|
|
require.NoError(t, err)
|
|
update = MetaUpdate{
|
|
Role: data.CanonicalTimestampRole,
|
|
Version: 2,
|
|
Data: newJ,
|
|
}
|
|
|
|
store.UpdateCurrent("gun", update)
|
|
|
|
cDate, data, err := store.GetChecksum("gun", data.CanonicalTimestampRole, checksum)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, j, data)
|
|
// the creation date was sometime wthin the last minute
|
|
require.True(t, cDate.After(time.Now().Add(-1*time.Minute)))
|
|
require.True(t, cDate.Before(time.Now().Add(5*time.Second)))
|
|
}
|
|
|
|
func TestDBGetChecksumNotFound(t *testing.T) {
|
|
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
|
_, store := SetUpSQLite(t, tempBaseDir)
|
|
defer os.RemoveAll(tempBaseDir)
|
|
|
|
_, _, err = store.GetChecksum("gun", data.CanonicalTimestampRole, "12345")
|
|
require.Error(t, err)
|
|
require.IsType(t, ErrNotFound{}, err)
|
|
}
|