docs/server/storage/tuf_store.go

130 lines
4.5 KiB
Go

package storage
import (
"encoding/hex"
"fmt"
"time"
"github.com/docker/go/canonical/json"
"github.com/docker/notary"
"github.com/docker/notary/storage"
"github.com/docker/notary/tuf/data"
)
// TUFMetaStorage wraps a MetaStore in order to walk the TUF tree for GetCurrent in a consistent manner,
// by always starting from a current timestamp and then looking up other data by hash
type TUFMetaStorage struct {
MetaStore
// cached metadata by checksum
cachedMeta map[string]*storedMeta
}
// NewTUFMetaStorage instantiates a TUFMetaStorage instance
func NewTUFMetaStorage(m MetaStore) *TUFMetaStorage {
return &TUFMetaStorage{
MetaStore: m,
cachedMeta: make(map[string]*storedMeta),
}
}
type storedMeta struct {
data []byte
createupdate *time.Time
}
// GetCurrent gets a specific TUF record, by walking from the current Timestamp to other metadata by checksum
func (tms TUFMetaStorage) GetCurrent(gun, tufRole string) (*time.Time, []byte, error) {
timestampTime, timestampJSON, err := tms.MetaStore.GetCurrent(gun, data.CanonicalTimestampRole)
if err != nil {
return nil, nil, err
}
// If we wanted data for the timestamp role, we're done here
if tufRole == data.CanonicalTimestampRole {
return timestampTime, timestampJSON, nil
}
// If we want to lookup another role, walk to it via current timestamp --> snapshot by checksum --> desired role
timestampMeta := &data.SignedTimestamp{}
if err := json.Unmarshal(timestampJSON, timestampMeta); err != nil {
return nil, nil, fmt.Errorf("could not parse current timestamp")
}
snapshotChecksums, err := timestampMeta.GetSnapshot()
if err != nil || snapshotChecksums == nil {
return nil, nil, fmt.Errorf("could not retrieve latest snapshot checksum")
}
snapshotSha256Bytes, ok := snapshotChecksums.Hashes[notary.SHA256]
if !ok {
return nil, nil, fmt.Errorf("could not retrieve latest snapshot sha256")
}
snapshotSha256Hex := hex.EncodeToString(snapshotSha256Bytes[:])
// Check the cache if we have our snapshot data
var snapshotTime *time.Time
var snapshotJSON []byte
if cachedSnapshotData, ok := tms.cachedMeta[snapshotSha256Hex]; ok {
snapshotTime = cachedSnapshotData.createupdate
snapshotJSON = cachedSnapshotData.data
} else {
// Get the snapshot from the underlying store by checksum if it isn't cached yet
snapshotTime, snapshotJSON, err = tms.GetChecksum(gun, data.CanonicalSnapshotRole, snapshotSha256Hex)
if err != nil {
return nil, nil, err
}
// cache for subsequent lookups
tms.cachedMeta[snapshotSha256Hex] = &storedMeta{data: snapshotJSON, createupdate: snapshotTime}
}
// If we wanted data for the snapshot role, we're done here
if tufRole == data.CanonicalSnapshotRole {
return snapshotTime, snapshotJSON, nil
}
// If it's a different role, we should have the checksum in snapshot metadata, and we can use it to GetChecksum()
snapshotMeta := &data.SignedSnapshot{}
if err := json.Unmarshal(snapshotJSON, snapshotMeta); err != nil {
return nil, nil, fmt.Errorf("could not parse current snapshot")
}
roleMeta, err := snapshotMeta.GetMeta(tufRole)
if err != nil {
return nil, nil, err
}
roleSha256Bytes, ok := roleMeta.Hashes[notary.SHA256]
if !ok {
return nil, nil, fmt.Errorf("could not retrieve latest %s sha256", tufRole)
}
roleSha256Hex := hex.EncodeToString(roleSha256Bytes[:])
// check if we can retrieve this data from cache
if cachedRoleData, ok := tms.cachedMeta[roleSha256Hex]; ok {
return cachedRoleData.createupdate, cachedRoleData.data, nil
}
roleTime, roleJSON, err := tms.MetaStore.GetChecksum(gun, tufRole, roleSha256Hex)
if err != nil {
return nil, nil, err
}
// cache for subsequent lookups
tms.cachedMeta[roleSha256Hex] = &storedMeta{data: roleJSON, createupdate: roleTime}
return roleTime, roleJSON, nil
}
// GetChecksum gets a specific TUF record by checksum, also checking the internal cache
func (tms TUFMetaStorage) GetChecksum(gun, tufRole, checksum string) (*time.Time, []byte, error) {
if cachedRoleData, ok := tms.cachedMeta[checksum]; ok {
return cachedRoleData.createupdate, cachedRoleData.data, nil
}
roleTime, roleJSON, err := tms.MetaStore.GetChecksum(gun, tufRole, checksum)
if err != nil {
return nil, nil, err
}
// cache for subsequent lookups
tms.cachedMeta[checksum] = &storedMeta{data: roleJSON, createupdate: roleTime}
return roleTime, roleJSON, nil
}
// Bootstrap the store with tables if possible
func (tms TUFMetaStorage) Bootstrap() error {
if s, ok := tms.MetaStore.(storage.Bootstrapper); ok {
return s.Bootstrap()
}
return fmt.Errorf("store does not support bootstrapping")
}