| package client |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "net/http" |
| "path" |
| "time" |
| |
| "github.com/Sirupsen/logrus" |
| "github.com/docker/notary/client/changelist" |
| tuf "github.com/docker/notary/tuf" |
| "github.com/docker/notary/tuf/data" |
| "github.com/docker/notary/tuf/keys" |
| "github.com/docker/notary/tuf/store" |
| ) |
| |
| // Use this to initialize remote HTTPStores from the config settings |
| func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStore, error) { |
| return store.NewHTTPStore( |
| baseURL+"/v2/"+gun+"/_trust/tuf/", |
| "", |
| "json", |
| "", |
| "key", |
| rt, |
| ) |
| } |
| |
| func applyChangelist(repo *tuf.Repo, cl changelist.Changelist) error { |
| it, err := cl.NewIterator() |
| if err != nil { |
| return err |
| } |
| index := 0 |
| for it.HasNext() { |
| c, err := it.Next() |
| if err != nil { |
| return err |
| } |
| isDel := data.IsDelegation(c.Scope()) |
| switch { |
| case c.Scope() == changelist.ScopeTargets || isDel: |
| err = applyTargetsChange(repo, c) |
| case c.Scope() == changelist.ScopeRoot: |
| err = applyRootChange(repo, c) |
| default: |
| logrus.Debug("scope not supported: ", c.Scope()) |
| } |
| index++ |
| if err != nil { |
| return err |
| } |
| } |
| logrus.Debugf("applied %d change(s)", index) |
| return nil |
| } |
| |
| func applyTargetsChange(repo *tuf.Repo, c changelist.Change) error { |
| switch c.Type() { |
| case changelist.TypeTargetsTarget: |
| return changeTargetMeta(repo, c) |
| case changelist.TypeTargetsDelegation: |
| return changeTargetsDelegation(repo, c) |
| default: |
| return fmt.Errorf("only target meta and delegations changes supported") |
| } |
| } |
| |
| func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error { |
| switch c.Action() { |
| case changelist.ActionCreate: |
| td := changelist.TufDelegation{} |
| err := json.Unmarshal(c.Content(), &td) |
| if err != nil { |
| return err |
| } |
| r, err := repo.GetDelegation(c.Scope()) |
| if _, ok := err.(data.ErrNoSuchRole); err != nil && !ok { |
| // error that wasn't ErrNoSuchRole |
| return err |
| } |
| if err == nil { |
| // role existed |
| return data.ErrInvalidRole{ |
| Role: c.Scope(), |
| Reason: "cannot create a role that already exists", |
| } |
| } |
| // role doesn't exist, create brand new |
| r, err = td.ToNewRole(c.Scope()) |
| if err != nil { |
| return err |
| } |
| return repo.UpdateDelegations(r, td.AddKeys) |
| case changelist.ActionUpdate: |
| td := changelist.TufDelegation{} |
| err := json.Unmarshal(c.Content(), &td) |
| if err != nil { |
| return err |
| } |
| r, err := repo.GetDelegation(c.Scope()) |
| if err != nil { |
| return err |
| } |
| // role exists, merge |
| if err := r.AddPaths(td.AddPaths); err != nil { |
| return err |
| } |
| if err := r.AddPathHashPrefixes(td.AddPathHashPrefixes); err != nil { |
| return err |
| } |
| r.RemoveKeys(td.RemoveKeys) |
| r.RemovePaths(td.RemovePaths) |
| r.RemovePathHashPrefixes(td.RemovePathHashPrefixes) |
| return repo.UpdateDelegations(r, td.AddKeys) |
| case changelist.ActionDelete: |
| r := data.Role{Name: c.Scope()} |
| return repo.DeleteDelegation(r) |
| default: |
| return fmt.Errorf("unsupported action against delegations: %s", c.Action()) |
| } |
| |
| } |
| |
| // applies a function repeatedly, falling back on the parent role, until it no |
| // longer can |
| func doWithRoleFallback(role string, doFunc func(string) error) error { |
| for role == data.CanonicalTargetsRole || data.IsDelegation(role) { |
| err := doFunc(role) |
| if err == nil { |
| return nil |
| } |
| if _, ok := err.(data.ErrInvalidRole); !ok { |
| return err |
| } |
| role = path.Dir(role) |
| } |
| return data.ErrInvalidRole{Role: role} |
| } |
| |
| func changeTargetMeta(repo *tuf.Repo, c changelist.Change) error { |
| var err error |
| switch c.Action() { |
| case changelist.ActionCreate: |
| logrus.Debug("changelist add: ", c.Path()) |
| meta := &data.FileMeta{} |
| err = json.Unmarshal(c.Content(), meta) |
| if err != nil { |
| return err |
| } |
| files := data.Files{c.Path(): *meta} |
| |
| err = doWithRoleFallback(c.Scope(), func(role string) error { |
| _, e := repo.AddTargets(role, files) |
| return e |
| }) |
| if err != nil { |
| logrus.Errorf("couldn't add target to %s: %s", c.Scope(), err.Error()) |
| } |
| |
| case changelist.ActionDelete: |
| logrus.Debug("changelist remove: ", c.Path()) |
| |
| err = doWithRoleFallback(c.Scope(), func(role string) error { |
| return repo.RemoveTargets(role, c.Path()) |
| }) |
| if err != nil { |
| logrus.Errorf("couldn't remove target from %s: %s", c.Scope(), err.Error()) |
| } |
| |
| default: |
| logrus.Debug("action not yet supported: ", c.Action()) |
| } |
| return err |
| } |
| |
| func applyRootChange(repo *tuf.Repo, c changelist.Change) error { |
| var err error |
| switch c.Type() { |
| case changelist.TypeRootRole: |
| err = applyRootRoleChange(repo, c) |
| default: |
| logrus.Debug("type of root change not yet supported: ", c.Type()) |
| } |
| return err // might be nil |
| } |
| |
| func applyRootRoleChange(repo *tuf.Repo, c changelist.Change) error { |
| switch c.Action() { |
| case changelist.ActionCreate: |
| // replaces all keys for a role |
| d := &changelist.TufRootData{} |
| err := json.Unmarshal(c.Content(), d) |
| if err != nil { |
| return err |
| } |
| err = repo.ReplaceBaseKeys(d.RoleName, d.Keys...) |
| if err != nil { |
| return err |
| } |
| default: |
| logrus.Debug("action not yet supported for root: ", c.Action()) |
| } |
| return nil |
| } |
| |
| func nearExpiry(r *data.SignedRoot) bool { |
| plus6mo := time.Now().AddDate(0, 6, 0) |
| return r.Signed.Expires.Before(plus6mo) |
| } |
| |
| // Fetches a public key from a remote store, given a gun and role |
| func getRemoteKey(url, gun, role string, rt http.RoundTripper) (data.PublicKey, error) { |
| remote, err := getRemoteStore(url, gun, rt) |
| if err != nil { |
| return nil, err |
| } |
| rawPubKey, err := remote.GetKey(role) |
| if err != nil { |
| return nil, err |
| } |
| |
| pubKey, err := data.UnmarshalPublicKey(rawPubKey) |
| if err != nil { |
| return nil, err |
| } |
| |
| return pubKey, nil |
| } |
| |
| // add a key to a KeyDB, and create a role for the key and add it. |
| func addKeyForRole(kdb *keys.KeyDB, role string, key data.PublicKey) error { |
| theRole, err := data.NewRole(role, 1, []string{key.ID()}, nil, nil) |
| if err != nil { |
| return err |
| } |
| kdb.AddKey(key) |
| if err := kdb.AddRole(theRole); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // signs and serializes the metadata for a canonical role in a tuf repo to JSON |
| func serializeCanonicalRole(tufRepo *tuf.Repo, role string) (out []byte, err error) { |
| var s *data.Signed |
| switch { |
| case role == data.CanonicalRootRole: |
| s, err = tufRepo.SignRoot(data.DefaultExpires(role)) |
| case role == data.CanonicalSnapshotRole: |
| s, err = tufRepo.SignSnapshot(data.DefaultExpires(role)) |
| case tufRepo.Targets[role] != nil: |
| s, err = tufRepo.SignTargets( |
| role, data.DefaultExpires(data.CanonicalTargetsRole)) |
| default: |
| err = fmt.Errorf("%s not supported role to sign on the client", role) |
| } |
| |
| if err != nil { |
| return |
| } |
| |
| return json.Marshal(s) |
| } |