@@ -419,7 +419,7 @@ func (r *NotaryRepository) RemoveTarget(targetName string, roles ...string) erro
419419// subtree and also the "targets/x" subtree, as we will defer parsing it until
420420// we explicitly reach it in our iteration of the provided list of roles.
421421func (r * NotaryRepository ) ListTargets (roles ... string ) ([]* TargetWithRole , error ) {
422- _ , err := r .Update ()
422+ _ , err := r .Update (false )
423423 if err != nil {
424424 return nil , err
425425 }
@@ -479,7 +479,7 @@ func (r *NotaryRepository) listSubtree(targets map[string]*TargetWithRole, role
479479// will be returned
480480// See the IMPORTANT section on ListTargets above. Those roles also apply here.
481481func (r * NotaryRepository ) GetTargetByName (name string , roles ... string ) (* TargetWithRole , error ) {
482- c , err := r .Update ()
482+ c , err := r .Update (false )
483483 if err != nil {
484484 return nil , err
485485 }
@@ -514,7 +514,7 @@ func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) {
514514func (r * NotaryRepository ) Publish () error {
515515 var initialPublish bool
516516 // update first before publishing
517- _ , err := r .Update ()
517+ _ , err := r .Update (true )
518518 if err != nil {
519519 // If the remote is not aware of the repo, then this is being published
520520 // for the first time. Try to load from disk instead for publishing.
@@ -555,13 +555,21 @@ func (r *NotaryRepository) Publish() error {
555555 // we send anything to remote
556556 updatedFiles := make (map [string ][]byte )
557557
558- // check if our root file is nearing expiry. Resign if it is.
559- if nearExpiry (r .tufRepo .Root ) || r .tufRepo .Root .Dirty || initialPublish {
558+ // check if our root file is nearing expiry or dirty. Resign if it is. If
559+ // root is not dirty but we are publishing for the first time, then just
560+ // publish the existing root we have.
561+ if nearExpiry (r .tufRepo .Root ) || r .tufRepo .Root .Dirty {
560562 rootJSON , err := serializeCanonicalRole (r .tufRepo , data .CanonicalRootRole )
561563 if err != nil {
562564 return err
563565 }
564566 updatedFiles [data .CanonicalRootRole ] = rootJSON
567+ } else if initialPublish {
568+ rootJSON , err := r .tufRepo .Root .MarshalJSON ()
569+ if err != nil {
570+ return err
571+ }
572+ updatedFiles [data .CanonicalRootRole ] = rootJSON
565573 }
566574
567575 // iterate through all the targets files - if they are dirty, sign and update
@@ -714,75 +722,94 @@ func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error {
714722 return r .fileStore .SetMeta (data .CanonicalSnapshotRole , snapshotJSON )
715723}
716724
725+ // returns a properly constructed ErrRepositoryNotExist error based on this
726+ // repo's information
727+ func (r * NotaryRepository ) errRepositoryNotExist () error {
728+ host := r .baseURL
729+ parsed , err := url .Parse (r .baseURL )
730+ if err == nil {
731+ host = parsed .Host // try to exclude the scheme and any paths
732+ }
733+ return ErrRepositoryNotExist {remote : host , gun : r .gun }
734+ }
735+
717736// Update bootstraps a trust anchor (root.json) before updating all the
718737// metadata from the repo.
719- func (r * NotaryRepository ) Update () (* tufclient.Client , error ) {
720- c , err := r .bootstrapClient ()
738+ func (r * NotaryRepository ) Update (forWrite bool ) (* tufclient.Client , error ) {
739+ c , err := r .bootstrapClient (forWrite )
721740 if err != nil {
722741 if _ , ok := err .(store.ErrMetaNotFound ); ok {
723- host := r .baseURL
724- parsed , err := url .Parse (r .baseURL )
725- if err == nil {
726- host = parsed .Host // try to exclude the scheme and any paths
727- }
728- return nil , ErrRepositoryNotExist {remote : host , gun : r .gun }
742+ return nil , r .errRepositoryNotExist ()
729743 }
730744 return nil , err
731745 }
732746 err = c .Update ()
733747 if err != nil {
748+ if notFound , ok := err .(store.ErrMetaNotFound ); ok && notFound .Resource == data .CanonicalRootRole {
749+ return nil , r .errRepositoryNotExist ()
750+ }
734751 return nil , err
735752 }
736753 return c , nil
737754}
738755
739- func (r * NotaryRepository ) bootstrapClient () (* tufclient.Client , error ) {
740- var rootJSON []byte
741- remote , err := getRemoteStore (r .baseURL , r .gun , r .roundTrip )
742- if err == nil {
743- // if remote store successfully set up, try and get root from remote
744- rootJSON , err = remote .GetMeta ("root" , maxSize )
756+ // bootstrapClient attempts to bootstrap a root.json to be used as the trust
757+ // anchor for a repository. The checkInitialized argument indicates whether
758+ // we should always attempt to contact the server to determine if the repository
759+ // is initialized or not. If set to true, we will always attempt to download
760+ // and return an error if the remote repository errors.
761+ func (r * NotaryRepository ) bootstrapClient (checkInitialized bool ) (* tufclient.Client , error ) {
762+ var (
763+ rootJSON []byte
764+ err error
765+ signedRoot * data.SignedRoot
766+ )
767+ // try to read root from cache first. We will trust this root
768+ // until we detect a problem during update which will cause
769+ // us to download a new root and perform a rotation.
770+ rootJSON , cachedRootErr := r .fileStore .GetMeta ("root" , maxSize )
771+
772+ if cachedRootErr == nil {
773+ signedRoot , cachedRootErr = r .validateRoot (rootJSON )
745774 }
746775
747- // if remote store couldn't be setup, or we failed to get a root from it
748- // load the root from cache (offline operation)
749- if err != nil {
750- if err , ok := err .(store. ErrMetaNotFound ); ok {
751- // if the error was MetaNotFound then we successfully contacted
752- // the store and it doesn't know about the repo.
753- return nil , err
754- }
755- result , cacheErr := r . fileStore .GetMeta ("root" , maxSize )
756- if cacheErr != nil {
757- // if cache didn't return a root, we cannot proceed - just return
758- // the original error.
776+ remote , remoteErr := getRemoteStore ( r . baseURL , r . gun , r . roundTrip )
777+ if remoteErr != nil {
778+ logrus . Error ( remoteErr )
779+ } else if cachedRootErr != nil || checkInitialized {
780+ // remoteErr was nil and we had a cachedRootErr (or are specifically
781+ // checking for initialization of the repo) .
782+
783+ // if remote store successfully set up, try and get root from remote
784+ tmpJSON , err := remote .GetMeta ("root" , maxSize )
785+ if err != nil {
786+ // we didn't have a root in cache and were unable to load one from
787+ // the server. Nothing we can do but error.
759788 return nil , err
760789 }
761- rootJSON = result
762- logrus .Debugf (
763- "Using local cache instead of remote due to failure: %s" , err .Error ())
764- }
765- // can't just unmarshal into SignedRoot because validate root
766- // needs the root.Signed field to still be []byte for signature
767- // validation
768- root := & data.Signed {}
769- err = json .Unmarshal (rootJSON , root )
770- if err != nil {
771- return nil , err
772- }
790+ if cachedRootErr != nil {
791+ // we always want to use the downloaded root if there was a cache
792+ // error.
793+ signedRoot , err = r .validateRoot (tmpJSON )
794+ if err != nil {
795+ return nil , err
796+ }
773797
774- err = r .CertManager .ValidateRoot (root , r .gun )
775- if err != nil {
776- return nil , err
798+ err = r .fileStore .SetMeta ("root" , tmpJSON )
799+ if err != nil {
800+ // if we can't write cache we should still continue, just log error
801+ logrus .Errorf ("could not save root to cache: %s" , err .Error ())
802+ }
803+ }
777804 }
778805
779806 kdb := keys .NewDB ()
780807 r .tufRepo = tuf .NewRepo (kdb , r .CryptoService )
781808
782- signedRoot , err := data .RootFromSigned (root )
783- if err != nil {
784- return nil , err
809+ if signedRoot == nil {
810+ return nil , ErrRepoNotInitialized {}
785811 }
812+
786813 err = r .tufRepo .SetRoot (signedRoot )
787814 if err != nil {
788815 return nil , err
@@ -796,6 +823,28 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) {
796823 ), nil
797824}
798825
826+ // validateRoot MUST only be used during bootstrapping. It will only validate
827+ // signatures of the root based on known keys, not expiry or other metadata.
828+ // This is so that an out of date root can be loaded to be used in a rotation
829+ // should the TUF update process detect a problem.
830+ func (r * NotaryRepository ) validateRoot (rootJSON []byte ) (* data.SignedRoot , error ) {
831+ // can't just unmarshal into SignedRoot because validate root
832+ // needs the root.Signed field to still be []byte for signature
833+ // validation
834+ root := & data.Signed {}
835+ err := json .Unmarshal (rootJSON , root )
836+ if err != nil {
837+ return nil , err
838+ }
839+
840+ err = r .CertManager .ValidateRoot (root , r .gun )
841+ if err != nil {
842+ return nil , err
843+ }
844+
845+ return data .RootFromSigned (root )
846+ }
847+
799848// RotateKey removes all existing keys associated with the role, and either
800849// creates and adds one new key or delegates managing the key to the server.
801850// These changes are staged in a changelist until publish is called.
0 commit comments