@@ -419,7 +419,7 @@ func (r *NotaryRepository) RemoveTarget(targetName string, roles ...string) erro
419
419
// subtree and also the "targets/x" subtree, as we will defer parsing it until
420
420
// we explicitly reach it in our iteration of the provided list of roles.
421
421
func (r * NotaryRepository ) ListTargets (roles ... string ) ([]* TargetWithRole , error ) {
422
- _ , err := r .Update ()
422
+ _ , err := r .Update (false )
423
423
if err != nil {
424
424
return nil , err
425
425
}
@@ -479,7 +479,7 @@ func (r *NotaryRepository) listSubtree(targets map[string]*TargetWithRole, role
479
479
// will be returned
480
480
// See the IMPORTANT section on ListTargets above. Those roles also apply here.
481
481
func (r * NotaryRepository ) GetTargetByName (name string , roles ... string ) (* TargetWithRole , error ) {
482
- c , err := r .Update ()
482
+ c , err := r .Update (false )
483
483
if err != nil {
484
484
return nil , err
485
485
}
@@ -514,7 +514,7 @@ func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) {
514
514
func (r * NotaryRepository ) Publish () error {
515
515
var initialPublish bool
516
516
// update first before publishing
517
- _ , err := r .Update ()
517
+ _ , err := r .Update (true )
518
518
if err != nil {
519
519
// If the remote is not aware of the repo, then this is being published
520
520
// for the first time. Try to load from disk instead for publishing.
@@ -555,13 +555,21 @@ func (r *NotaryRepository) Publish() error {
555
555
// we send anything to remote
556
556
updatedFiles := make (map [string ][]byte )
557
557
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 {
560
562
rootJSON , err := serializeCanonicalRole (r .tufRepo , data .CanonicalRootRole )
561
563
if err != nil {
562
564
return err
563
565
}
564
566
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
565
573
}
566
574
567
575
// iterate through all the targets files - if they are dirty, sign and update
@@ -714,75 +722,94 @@ func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error {
714
722
return r .fileStore .SetMeta (data .CanonicalSnapshotRole , snapshotJSON )
715
723
}
716
724
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
+
717
736
// Update bootstraps a trust anchor (root.json) before updating all the
718
737
// 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 )
721
740
if err != nil {
722
741
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 ()
729
743
}
730
744
return nil , err
731
745
}
732
746
err = c .Update ()
733
747
if err != nil {
748
+ if notFound , ok := err .(store.ErrMetaNotFound ); ok && notFound .Resource == data .CanonicalRootRole {
749
+ return nil , r .errRepositoryNotExist ()
750
+ }
734
751
return nil , err
735
752
}
736
753
return c , nil
737
754
}
738
755
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 )
745
774
}
746
775
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.
759
788
return nil , err
760
789
}
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
+ }
773
797
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
+ }
777
804
}
778
805
779
806
kdb := keys .NewDB ()
780
807
r .tufRepo = tuf .NewRepo (kdb , r .CryptoService )
781
808
782
- signedRoot , err := data .RootFromSigned (root )
783
- if err != nil {
784
- return nil , err
809
+ if signedRoot == nil {
810
+ return nil , ErrRepoNotInitialized {}
785
811
}
812
+
786
813
err = r .tufRepo .SetRoot (signedRoot )
787
814
if err != nil {
788
815
return nil , err
@@ -796,6 +823,28 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) {
796
823
), nil
797
824
}
798
825
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
+
799
848
// RotateKey removes all existing keys associated with the role, and either
800
849
// creates and adds one new key or delegates managing the key to the server.
801
850
// These changes are staged in a changelist until publish is called.
0 commit comments