Prototype signed catalog fallback (T-101)
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
package catalog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var ErrNoValidCatalog = errors.New("no valid catalog available")
|
||||
|
||||
// Fetcher obtains a signed Catalog document from a remote or test source.
|
||||
type Fetcher interface {
|
||||
Fetch(context.Context) ([]byte, error)
|
||||
}
|
||||
|
||||
// FetchFunc adapts a function to Fetcher.
|
||||
type FetchFunc func(context.Context) ([]byte, error)
|
||||
|
||||
func (function FetchFunc) Fetch(ctx context.Context) ([]byte, error) {
|
||||
return function(ctx)
|
||||
}
|
||||
|
||||
// Cache stores the last verified signed document.
|
||||
type Cache interface {
|
||||
Load() ([]byte, error)
|
||||
Store([]byte) error
|
||||
}
|
||||
|
||||
// LoadSource describes where a verified result came from.
|
||||
type LoadSource string
|
||||
|
||||
const (
|
||||
SourceRemote LoadSource = "remote"
|
||||
SourceCache LoadSource = "cache"
|
||||
)
|
||||
|
||||
// LoadResult returns verified bytes and a non-fatal refresh/cache warning.
|
||||
type LoadResult struct {
|
||||
Document VerifiedDocument
|
||||
Source LoadSource
|
||||
Warning error
|
||||
}
|
||||
|
||||
// LoadError preserves both the refresh and cache failure.
|
||||
type LoadError struct {
|
||||
Refresh error
|
||||
Cache error
|
||||
}
|
||||
|
||||
func (err *LoadError) Error() string {
|
||||
return fmt.Sprintf("%s: refresh=%v; cache=%v", ErrNoValidCatalog, err.Refresh, err.Cache)
|
||||
}
|
||||
|
||||
func (err *LoadError) Unwrap() error {
|
||||
return ErrNoValidCatalog
|
||||
}
|
||||
|
||||
// Loader verifies remote data before storing it and re-verifies cache fallback.
|
||||
type Loader struct {
|
||||
verifier Verifier
|
||||
fetcher Fetcher
|
||||
cache Cache
|
||||
}
|
||||
|
||||
func NewLoader(verifier Verifier, fetcher Fetcher, cache Cache) *Loader {
|
||||
return &Loader{
|
||||
verifier: verifier,
|
||||
fetcher: fetcher,
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
||||
// Load prefers a verified remote document and falls back to verified cache.
|
||||
func (loader *Loader) Load(ctx context.Context) (LoadResult, error) {
|
||||
remoteBytes, refreshErr := loader.fetcher.Fetch(ctx)
|
||||
if refreshErr == nil {
|
||||
verified, verifyErr := loader.verifier.Verify(remoteBytes)
|
||||
if verifyErr == nil {
|
||||
storeErr := loader.cache.Store(verified.Bytes)
|
||||
return LoadResult{
|
||||
Document: verified,
|
||||
Source: SourceRemote,
|
||||
Warning: storeErr,
|
||||
}, nil
|
||||
}
|
||||
refreshErr = verifyErr
|
||||
}
|
||||
|
||||
cachedBytes, cacheErr := loader.cache.Load()
|
||||
if cacheErr == nil {
|
||||
var verified VerifiedDocument
|
||||
verified, cacheErr = loader.verifier.Verify(cachedBytes)
|
||||
if cacheErr == nil {
|
||||
return LoadResult{
|
||||
Document: verified,
|
||||
Source: SourceCache,
|
||||
Warning: refreshErr,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return LoadResult{}, &LoadError{
|
||||
Refresh: refreshErr,
|
||||
Cache: cacheErr,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user