func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { var rootJSON []byte remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip) if err == nil { // if remote store successfully set up, try and get root from remote rootJSON, err = remote.GetMeta("root", maxSize) } // if remote store couldn't be setup, or we failed to get a root from it // load the root from cache (offline operation) if err != nil { if err, ok := err.(store.ErrMetaNotFound); ok { // if the error was MetaNotFound then we successfully contacted // the store and it doesn't know about the repo. return nil, err } result, cacheErr := r.fileStore.GetMeta("root", maxSize) if cacheErr != nil { // if cache didn't return a root, we cannot proceed - just return // the original error. return nil, err } rootJSON = result logrus.Debugf( "Using local cache instead of remote due to failure: %s", err.Error()) } // can't just unmarshal into SignedRoot because validate root // needs the root.Signed field to still be []byte for signature // validation root := &data.Signed{} err = json.Unmarshal(rootJSON, root) if err != nil { return nil, err } err = r.CertManager.ValidateRoot(root, r.gun) if err != nil { return nil, err } kdb := keys.NewDB() r.tufRepo = tuf.NewRepo(kdb, r.CryptoService) signedRoot, err := data.RootFromSigned(root) if err != nil { return nil, err } err = r.tufRepo.SetRoot(signedRoot) if err != nil { return nil, err } return tufclient.NewClient( r.tufRepo, remote, kdb, r.fileStore, ), nil }
// bootstrapClient attempts to bootstrap a root.json to be used as the trust // anchor for a repository. The checkInitialized argument indicates whether // we should always attempt to contact the server to determine if the repository // is initialized or not. If set to true, we will always attempt to download // and return an error if the remote repository errors. func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Client, error) { var ( rootJSON []byte err error signedRoot *data.SignedRoot ) // try to read root from cache first. We will trust this root // until we detect a problem during update which will cause // us to download a new root and perform a rotation. rootJSON, cachedRootErr := r.fileStore.GetMeta("root", maxSize) if cachedRootErr == nil { signedRoot, cachedRootErr = r.validateRoot(rootJSON) } remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip) if remoteErr != nil { logrus.Error(remoteErr) } else if cachedRootErr != nil || checkInitialized { // remoteErr was nil and we had a cachedRootErr (or are specifically // checking for initialization of the repo). // if remote store successfully set up, try and get root from remote tmpJSON, err := remote.GetMeta("root", maxSize) if err != nil { // we didn't have a root in cache and were unable to load one from // the server. Nothing we can do but error. return nil, err } if cachedRootErr != nil { // we always want to use the downloaded root if there was a cache // error. signedRoot, err = r.validateRoot(tmpJSON) if err != nil { return nil, err } err = r.fileStore.SetMeta("root", tmpJSON) if err != nil { // if we can't write cache we should still continue, just log error logrus.Errorf("could not save root to cache: %s", err.Error()) } } } kdb := keys.NewDB() r.tufRepo = tuf.NewRepo(kdb, r.CryptoService) if signedRoot == nil { return nil, ErrRepoNotInitialized{} } err = r.tufRepo.SetRoot(signedRoot) if err != nil { return nil, err } return tufclient.NewClient( r.tufRepo, remote, kdb, r.fileStore, ), nil }
// bootstrapClient attempts to bootstrap a root.json to be used as the trust // anchor for a repository. The checkInitialized argument indicates whether // we should always attempt to contact the server to determine if the repository // is initialized or not. If set to true, we will always attempt to download // and return an error if the remote repository errors. // // Populates a tuf.RepoBuilder with this root metadata (only use // tufclient.Client.Update to load the rest). // // Fails if the remote server is reachable and does not know the repo // (i.e. before the first r.Publish()), in which case the error is // store.ErrMetaNotFound, or if the root metadata (from whichever source is used) // is not trusted. // // Returns a tufclient.Client for the remote server, which may not be actually // operational (if the URL is invalid but a root.json is cached). func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Client, error) { minVersion := 1 // the old root on disk should not be validated against any trust pinning configuration // because if we have an old root, it itself is the thing that pins trust oldBuilder := tuf.NewRepoBuilder(r.gun, r.CryptoService, trustpinning.TrustPinConfig{}) // by default, we want to use the trust pinning configuration on any new root that we download newBuilder := tuf.NewRepoBuilder(r.gun, r.CryptoService, r.trustPinning) // Try to read root from cache first. We will trust this root until we detect a problem // during update which will cause us to download a new root and perform a rotation. // If we have an old root, and it's valid, then we overwrite the newBuilder to be one // preloaded with the old root or one which uses the old root for trust bootstrapping. if rootJSON, err := r.fileStore.GetMeta(data.CanonicalRootRole, store.NoSizeLimit); err == nil { // if we can't load the cached root, fail hard because that is how we pin trust if err := oldBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, true); err != nil { return nil, err } // again, the root on disk is the source of trust pinning, so use an empty trust // pinning configuration newBuilder = tuf.NewRepoBuilder(r.gun, r.CryptoService, trustpinning.TrustPinConfig{}) if err := newBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, false); err != nil { // Ok, the old root is expired - we want to download a new one. But we want to use the // old root to verify the new root, so bootstrap a new builder with the old builder minVersion = oldBuilder.GetLoadedVersion(data.CanonicalRootRole) newBuilder = oldBuilder.BootstrapNewBuilder() } } remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip) if remoteErr != nil { logrus.Error(remoteErr) } else if !newBuilder.IsLoaded(data.CanonicalRootRole) || checkInitialized { // remoteErr was nil and we were not able to load a root from cache or // are specifically checking for initialization of the repo. // if remote store successfully set up, try and get root from remote // We don't have any local data to determine the size of root, so try the maximum (though it is restricted at 100MB) tmpJSON, err := remote.GetMeta(data.CanonicalRootRole, store.NoSizeLimit) if err != nil { // we didn't have a root in cache and were unable to load one from // the server. Nothing we can do but error. return nil, err } if !newBuilder.IsLoaded(data.CanonicalRootRole) { // we always want to use the downloaded root if we couldn't load from cache if err := newBuilder.Load(data.CanonicalRootRole, tmpJSON, minVersion, false); err != nil { return nil, err } err = r.fileStore.SetMeta(data.CanonicalRootRole, tmpJSON) if err != nil { // if we can't write cache we should still continue, just log error logrus.Errorf("could not save root to cache: %s", err.Error()) } } } // We can only get here if remoteErr != nil (hence we don't download any new root), // and there was no root on disk if !newBuilder.IsLoaded(data.CanonicalRootRole) { return nil, ErrRepoNotInitialized{} } return tufclient.NewClient(oldBuilder, newBuilder, remote, r.fileStore), nil }