2019-03-18 10:55:06 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package trust
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-11-16 00:59:32 +00:00
|
|
|
"math/rand"
|
|
|
|
"sort"
|
2019-03-18 10:55:06 +00:00
|
|
|
"sync"
|
2019-11-16 00:59:32 +00:00
|
|
|
"time"
|
2019-03-18 10:55:06 +00:00
|
|
|
|
2019-04-12 15:28:27 +01:00
|
|
|
"github.com/zeebo/errs"
|
2019-11-16 00:59:32 +00:00
|
|
|
"go.uber.org/zap"
|
2019-06-04 13:31:39 +01:00
|
|
|
monkit "gopkg.in/spacemonkeygo/monkit.v2"
|
2019-04-12 15:28:27 +01:00
|
|
|
|
2019-03-18 10:55:06 +00:00
|
|
|
"storj.io/storj/pkg/identity"
|
2019-09-19 05:46:39 +01:00
|
|
|
"storj.io/storj/pkg/rpc"
|
2019-07-28 06:55:36 +01:00
|
|
|
"storj.io/storj/pkg/signing"
|
2019-03-18 10:55:06 +00:00
|
|
|
"storj.io/storj/pkg/storj"
|
2019-11-16 00:59:32 +00:00
|
|
|
"storj.io/storj/private/sync2"
|
2019-03-18 10:55:06 +00:00
|
|
|
)
|
|
|
|
|
2019-04-12 15:28:27 +01:00
|
|
|
// Error is the default error class
|
2019-11-16 00:59:32 +00:00
|
|
|
var (
|
|
|
|
Error = errs.Class("trust")
|
2019-07-17 19:14:44 +01:00
|
|
|
|
2019-11-16 00:59:32 +00:00
|
|
|
mon = monkit.Package()
|
|
|
|
)
|
|
|
|
|
|
|
|
// IdentityResolver resolves peer identities from a node URL
|
|
|
|
type IdentityResolver interface {
|
|
|
|
// ResolveIdentity returns the peer identity of the peer located at the Node URL
|
|
|
|
ResolveIdentity(ctx context.Context, url storj.NodeURL) (*identity.PeerIdentity, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IdentityResolverFunc is a convenience type for implementing IdentityResolver using a
|
|
|
|
// function literal.
|
|
|
|
type IdentityResolverFunc func(ctx context.Context, url storj.NodeURL) (*identity.PeerIdentity, error)
|
|
|
|
|
|
|
|
// ResolveIdentity returns the peer identity of the peer located at the Node URL
|
|
|
|
func (fn IdentityResolverFunc) ResolveIdentity(ctx context.Context, url storj.NodeURL) (*identity.PeerIdentity, error) {
|
|
|
|
return fn(ctx, url)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dialer implements an IdentityResolver using an RPC dialer
|
|
|
|
func Dialer(dialer rpc.Dialer) IdentityResolver {
|
|
|
|
return IdentityResolverFunc(func(ctx context.Context, url storj.NodeURL) (_ *identity.PeerIdentity, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
conn, err := dialer.DialAddressID(ctx, url.Address, url.ID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer func() { err = errs.Combine(err, conn.Close()) }()
|
|
|
|
return conn.PeerIdentity()
|
|
|
|
})
|
|
|
|
}
|
2019-04-12 15:28:27 +01:00
|
|
|
|
2019-03-18 10:55:06 +00:00
|
|
|
// Pool implements different peer verifications.
|
2019-09-10 14:24:16 +01:00
|
|
|
//
|
|
|
|
// architecture: Service
|
2019-03-18 10:55:06 +00:00
|
|
|
type Pool struct {
|
2019-11-16 00:59:32 +00:00
|
|
|
log *zap.Logger
|
|
|
|
resolver IdentityResolver
|
|
|
|
refreshInterval time.Duration
|
|
|
|
|
|
|
|
listMu sync.Mutex
|
|
|
|
list *List
|
2019-03-18 10:55:06 +00:00
|
|
|
|
2019-11-16 00:59:32 +00:00
|
|
|
satellitesMu sync.RWMutex
|
|
|
|
satellites map[storj.NodeID]*satelliteInfoCache
|
2019-03-18 10:55:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// satelliteInfoCache caches identity information about a satellite
|
|
|
|
type satelliteInfoCache struct {
|
2019-04-24 09:13:48 +01:00
|
|
|
mu sync.Mutex
|
2019-07-17 19:14:44 +01:00
|
|
|
url storj.NodeURL
|
2019-03-18 10:55:06 +00:00
|
|
|
identity *identity.PeerIdentity
|
|
|
|
}
|
|
|
|
|
2019-07-17 19:14:44 +01:00
|
|
|
// NewPool creates a new trust pool of the specified list of trusted satellites.
|
2019-11-16 00:59:32 +00:00
|
|
|
func NewPool(log *zap.Logger, resolver IdentityResolver, config Config) (*Pool, error) {
|
2019-03-18 10:55:06 +00:00
|
|
|
// TODO: preload all satellite peer identities
|
|
|
|
|
2019-11-16 00:59:32 +00:00
|
|
|
cache, err := LoadCache(config.CachePath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-03-18 10:55:06 +00:00
|
|
|
|
2019-11-16 00:59:32 +00:00
|
|
|
list, err := NewList(log, config.Sources, config.Exclusions.Rules, cache)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-03-18 10:55:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &Pool{
|
2019-11-16 00:59:32 +00:00
|
|
|
log: log,
|
|
|
|
resolver: resolver,
|
|
|
|
refreshInterval: config.RefreshInterval,
|
|
|
|
list: list,
|
|
|
|
satellites: make(map[storj.NodeID]*satelliteInfoCache),
|
2019-03-18 10:55:06 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-11-16 00:59:32 +00:00
|
|
|
// Run periodically refreshes the pool. The initial refresh is intended to
|
|
|
|
// happen before run is call. Therefore Run does not refresh right away.
|
|
|
|
func (pool *Pool) Run(ctx context.Context) error {
|
|
|
|
for {
|
|
|
|
refreshAfter := jitter(pool.refreshInterval)
|
|
|
|
pool.log.Info("Scheduling next refresh", zap.Duration("after", refreshAfter))
|
|
|
|
if !sync2.Sleep(ctx, refreshAfter) {
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
if err := pool.Refresh(ctx); err != nil {
|
|
|
|
pool.log.Error("Failed to refresh", zap.Error(err))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-18 10:55:06 +00:00
|
|
|
// VerifySatelliteID checks whether id corresponds to a trusted satellite.
|
2019-06-04 13:31:39 +01:00
|
|
|
func (pool *Pool) VerifySatelliteID(ctx context.Context, id storj.NodeID) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2019-03-18 10:55:06 +00:00
|
|
|
|
2019-11-16 00:59:32 +00:00
|
|
|
_, err = pool.getInfo(id)
|
|
|
|
return err
|
2019-03-18 10:55:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetSignee gets the corresponding signee for verifying signatures.
|
2019-04-08 21:31:20 +01:00
|
|
|
// It ignores passed in ctx cancellation to avoid miscaching between concurrent requests.
|
2019-06-04 13:31:39 +01:00
|
|
|
func (pool *Pool) GetSignee(ctx context.Context, id storj.NodeID) (_ signing.Signee, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2019-11-16 00:59:32 +00:00
|
|
|
info, err := pool.getInfo(id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-03-18 10:55:06 +00:00
|
|
|
}
|
|
|
|
|
2019-04-24 09:13:48 +01:00
|
|
|
info.mu.Lock()
|
|
|
|
defer info.mu.Unlock()
|
2019-04-12 15:28:27 +01:00
|
|
|
|
2019-04-24 09:13:48 +01:00
|
|
|
if info.identity == nil {
|
2019-11-16 00:59:32 +00:00
|
|
|
identity, err := pool.resolver.ResolveIdentity(ctx, info.url)
|
2019-04-24 09:13:48 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
2019-04-12 15:28:27 +01:00
|
|
|
info.identity = identity
|
2019-04-24 09:13:48 +01:00
|
|
|
}
|
2019-03-18 10:55:06 +00:00
|
|
|
|
|
|
|
return signing.SigneeFromPeerIdentity(info.identity), nil
|
|
|
|
}
|
2019-06-21 23:48:52 +01:00
|
|
|
|
|
|
|
// GetSatellites returns a slice containing all trusted satellites
|
|
|
|
func (pool *Pool) GetSatellites(ctx context.Context) (satellites []storj.NodeID) {
|
|
|
|
defer mon.Task()(&ctx)(nil)
|
2019-11-16 00:59:32 +00:00
|
|
|
for sat := range pool.satellites {
|
2019-06-21 23:48:52 +01:00
|
|
|
satellites = append(satellites, sat)
|
|
|
|
}
|
2019-11-16 00:59:32 +00:00
|
|
|
sort.Sort(storj.NodeIDList(satellites))
|
2019-06-21 23:48:52 +01:00
|
|
|
return satellites
|
|
|
|
}
|
2019-07-03 18:29:18 +01:00
|
|
|
|
|
|
|
// GetAddress returns the address of a satellite in the trusted list
|
|
|
|
func (pool *Pool) GetAddress(ctx context.Context, id storj.NodeID) (_ string, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2019-11-16 00:59:32 +00:00
|
|
|
info, err := pool.getInfo(id)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return info.url.Address, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Refresh refreshes the set of trusted satellites in the pool. Concurrent
|
|
|
|
// callers will be synchronized so only one proceeds at a time.
|
|
|
|
func (pool *Pool) Refresh(ctx context.Context) error {
|
|
|
|
urls, err := pool.fetchURLs(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
pool.satellitesMu.Lock()
|
|
|
|
defer pool.satellitesMu.Unlock()
|
|
|
|
|
|
|
|
// add/update trusted IDs
|
|
|
|
trustedIDs := make(map[storj.NodeID]struct{})
|
|
|
|
for _, url := range urls {
|
|
|
|
trustedIDs[url.ID] = struct{}{}
|
|
|
|
|
|
|
|
info, ok := pool.satellites[url.ID]
|
|
|
|
if !ok {
|
|
|
|
info = &satelliteInfoCache{
|
|
|
|
url: url,
|
|
|
|
}
|
|
|
|
pool.log.Debug("Satellite is trusted", zap.String("id", url.ID.String()))
|
|
|
|
pool.satellites[url.ID] = info
|
|
|
|
}
|
|
|
|
|
|
|
|
// update the URL address and reset the identity if it changed
|
|
|
|
if info.url.Address != url.Address {
|
|
|
|
pool.log.Debug("Satellite address updated; identity cache purged",
|
|
|
|
zap.String("id", url.ID.String()),
|
|
|
|
zap.String("old", info.url.Address),
|
|
|
|
zap.String("new", url.Address),
|
|
|
|
)
|
|
|
|
info.url.Address = url.Address
|
|
|
|
info.identity = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove trusted IDs that are no longer in the URL list
|
|
|
|
for id := range pool.satellites {
|
|
|
|
if _, ok := trustedIDs[id]; !ok {
|
|
|
|
pool.log.Debug("Satellite is no longer trusted", zap.String("id", id.String()))
|
|
|
|
delete(pool.satellites, id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2019-07-03 18:29:18 +01:00
|
|
|
|
2019-11-16 00:59:32 +00:00
|
|
|
func (pool *Pool) getInfo(id storj.NodeID) (*satelliteInfoCache, error) {
|
|
|
|
pool.satellitesMu.RLock()
|
|
|
|
defer pool.satellitesMu.RUnlock()
|
|
|
|
|
|
|
|
info, ok := pool.satellites[id]
|
2019-07-03 18:29:18 +01:00
|
|
|
if !ok {
|
2019-11-16 00:59:32 +00:00
|
|
|
return nil, Error.New("satellite %q is untrusted", id)
|
2019-07-03 18:29:18 +01:00
|
|
|
}
|
2019-11-16 00:59:32 +00:00
|
|
|
return info, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pool *Pool) fetchURLs(ctx context.Context) ([]storj.NodeURL, error) {
|
|
|
|
// Typically there will only be one caller of refresh (i.e. Run()) but
|
|
|
|
// if at some point we might want on-demand refresh, and *List is designed
|
|
|
|
// to be used by a single goroutine (don't want multiple callers racing
|
|
|
|
// on the cache, etc).
|
|
|
|
pool.listMu.Lock()
|
|
|
|
defer pool.listMu.Unlock()
|
|
|
|
return pool.list.FetchURLs(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
func jitter(t time.Duration) time.Duration {
|
|
|
|
nanos := rand.NormFloat64()*float64(t/4) + float64(t)
|
|
|
|
if nanos <= 0 {
|
|
|
|
nanos = 1
|
|
|
|
}
|
|
|
|
return time.Duration(nanos)
|
2019-07-03 18:29:18 +01:00
|
|
|
}
|