2014-10-02 01:26:06 +00:00
|
|
|
package trust
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/x509"
|
|
|
|
"errors"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2014-10-24 17:12:35 +00:00
|
|
|
log "github.com/Sirupsen/logrus"
|
2014-10-02 01:26:06 +00:00
|
|
|
"github.com/docker/libtrust/trustgraph"
|
|
|
|
)
|
|
|
|
|
|
|
|
type TrustStore struct {
|
|
|
|
path string
|
|
|
|
caPool *x509.CertPool
|
|
|
|
graph trustgraph.TrustGraph
|
|
|
|
expiration time.Time
|
|
|
|
fetcher *time.Timer
|
|
|
|
fetchTime time.Duration
|
|
|
|
autofetch bool
|
|
|
|
httpClient *http.Client
|
|
|
|
baseEndpoints map[string]*url.URL
|
|
|
|
|
|
|
|
sync.RWMutex
|
|
|
|
}
|
|
|
|
|
|
|
|
// defaultFetchtime represents the starting duration to wait between
|
|
|
|
// fetching sections of the graph. Unsuccessful fetches should
|
|
|
|
// increase time between fetching.
|
|
|
|
const defaultFetchtime = 45 * time.Second
|
|
|
|
|
|
|
|
var baseEndpoints = map[string]string{"official": "https://dvjy3tqbc323p.cloudfront.net/trust/official.json"}
|
|
|
|
|
|
|
|
func NewTrustStore(path string) (*TrustStore, error) {
|
|
|
|
abspath, err := filepath.Abs(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create base graph url map
|
|
|
|
endpoints := map[string]*url.URL{}
|
|
|
|
for name, endpoint := range baseEndpoints {
|
|
|
|
u, err := url.Parse(endpoint)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
endpoints[name] = u
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load grant files
|
|
|
|
t := &TrustStore{
|
|
|
|
path: abspath,
|
|
|
|
caPool: nil,
|
|
|
|
httpClient: &http.Client{},
|
|
|
|
fetchTime: time.Millisecond,
|
|
|
|
baseEndpoints: endpoints,
|
|
|
|
}
|
|
|
|
|
|
|
|
err = t.reload()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TrustStore) reload() error {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
matches, err := filepath.Glob(filepath.Join(t.path, "*.json"))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
statements := make([]*trustgraph.Statement, len(matches))
|
|
|
|
for i, match := range matches {
|
|
|
|
f, err := os.Open(match)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
statements[i], err = trustgraph.LoadStatement(f, nil)
|
|
|
|
if err != nil {
|
|
|
|
f.Close()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
f.Close()
|
|
|
|
}
|
|
|
|
if len(statements) == 0 {
|
|
|
|
if t.autofetch {
|
|
|
|
log.Debugf("No grants, fetching")
|
|
|
|
t.fetcher = time.AfterFunc(t.fetchTime, t.fetch)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
grants, expiration, err := trustgraph.CollapseStatements(statements, true)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
t.expiration = expiration
|
|
|
|
t.graph = trustgraph.NewMemoryGraph(grants)
|
|
|
|
log.Debugf("Reloaded graph with %d grants expiring at %s", len(grants), expiration)
|
|
|
|
|
|
|
|
if t.autofetch {
|
|
|
|
nextFetch := expiration.Sub(time.Now())
|
|
|
|
if nextFetch < 0 {
|
|
|
|
nextFetch = defaultFetchtime
|
|
|
|
} else {
|
|
|
|
nextFetch = time.Duration(0.8 * (float64)(nextFetch))
|
|
|
|
}
|
|
|
|
t.fetcher = time.AfterFunc(nextFetch, t.fetch)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TrustStore) fetchBaseGraph(u *url.URL) (*trustgraph.Statement, error) {
|
|
|
|
req := &http.Request{
|
|
|
|
Method: "GET",
|
|
|
|
URL: u,
|
|
|
|
Proto: "HTTP/1.1",
|
|
|
|
ProtoMajor: 1,
|
|
|
|
ProtoMinor: 1,
|
|
|
|
Header: make(http.Header),
|
|
|
|
Body: nil,
|
|
|
|
Host: u.Host,
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := t.httpClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if resp.StatusCode == 404 {
|
|
|
|
return nil, errors.New("base graph does not exist")
|
|
|
|
}
|
|
|
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
return trustgraph.LoadStatement(resp.Body, t.caPool)
|
|
|
|
}
|
|
|
|
|
|
|
|
// fetch retrieves updated base graphs. This function cannot error, it
|
|
|
|
// should only log errors
|
|
|
|
func (t *TrustStore) fetch() {
|
|
|
|
t.Lock()
|
|
|
|
defer t.Unlock()
|
|
|
|
|
|
|
|
if t.autofetch && t.fetcher == nil {
|
|
|
|
// Do nothing ??
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fetchCount := 0
|
|
|
|
for bg, ep := range t.baseEndpoints {
|
|
|
|
statement, err := t.fetchBaseGraph(ep)
|
|
|
|
if err != nil {
|
|
|
|
log.Infof("Trust graph fetch failed: %s", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
b, err := statement.Bytes()
|
|
|
|
if err != nil {
|
|
|
|
log.Infof("Bad trust graph statement: %s", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// TODO check if value differs
|
|
|
|
err = ioutil.WriteFile(path.Join(t.path, bg+".json"), b, 0600)
|
|
|
|
if err != nil {
|
|
|
|
log.Infof("Error writing trust graph statement: %s", err)
|
|
|
|
}
|
|
|
|
fetchCount++
|
|
|
|
}
|
|
|
|
log.Debugf("Fetched %d base graphs at %s", fetchCount, time.Now())
|
|
|
|
|
|
|
|
if fetchCount > 0 {
|
|
|
|
go func() {
|
|
|
|
err := t.reload()
|
|
|
|
if err != nil {
|
|
|
|
// TODO log
|
|
|
|
log.Infof("Reload of trust graph failed: %s", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
t.fetchTime = defaultFetchtime
|
|
|
|
t.fetcher = nil
|
|
|
|
} else if t.autofetch {
|
|
|
|
maxTime := 10 * defaultFetchtime
|
|
|
|
t.fetchTime = time.Duration(1.5 * (float64)(t.fetchTime+time.Second))
|
|
|
|
if t.fetchTime > maxTime {
|
|
|
|
t.fetchTime = maxTime
|
|
|
|
}
|
|
|
|
t.fetcher = time.AfterFunc(t.fetchTime, t.fetch)
|
|
|
|
}
|
|
|
|
}
|