blob: 885127ee5d6f1e9ba2d4a65339b1123788e4c7e0 [file] [log] [blame]
package trust
import (
"crypto/x509"
"errors"
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"sync"
"time"
"github.com/Sirupsen/logrus"
"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,
}
if err := t.reload(); 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 {
logrus.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)
logrus.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 {
logrus.Infof("Trust graph fetch failed: %s", err)
continue
}
b, err := statement.Bytes()
if err != nil {
logrus.Infof("Bad trust graph statement: %s", err)
continue
}
// TODO check if value differs
if err := ioutil.WriteFile(path.Join(t.path, bg+".json"), b, 0600); err != nil {
logrus.Infof("Error writing trust graph statement: %s", err)
}
fetchCount++
}
logrus.Debugf("Fetched %d base graphs at %s", fetchCount, time.Now())
if fetchCount > 0 {
go func() {
if err := t.reload(); err != nil {
logrus.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)
}
}