Skip to content

Commit

Permalink
Add backup for private credentials (#2323)
Browse files Browse the repository at this point in the history
and updated leia to v4 lib
  • Loading branch information
woutslakhorst authored Jul 12, 2023
1 parent 17f9fef commit 9275a2a
Show file tree
Hide file tree
Showing 14 changed files with 490 additions and 288 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ require (
github.com/nats-io/nats.go v1.27.1
github.com/nuts-foundation/crypto-ecies v0.0.0-20211207143025-5b84f9efce2b
github.com/nuts-foundation/go-did v0.5.1
github.com/nuts-foundation/go-leia/v3 v3.3.0
github.com/nuts-foundation/go-stoabs v1.9.0
github.com/piprate/json-gold v0.5.1-0.20230111113000-6ddbe6e6f19f
github.com/privacybydesign/irmago v0.12.6
Expand Down Expand Up @@ -141,6 +140,7 @@ require (
github.com/nats-io/nuid v1.0.1 // indirect
github.com/nightlyone/lockfile v1.0.0 // indirect
github.com/nuts-foundation/did-ockam v0.0.0-20230313074753-fafd938c948c // indirect
github.com/nuts-foundation/go-leia/v4 v4.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand All @@ -155,7 +155,7 @@ require (
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/templexxx/cpu v0.0.9 // indirect
github.com/templexxx/xorsimd v0.4.1 // indirect
github.com/tidwall/gjson v1.14.1 // indirect
github.com/tidwall/gjson v1.14.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a // indirect
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -452,8 +452,8 @@ github.com/nuts-foundation/did-ockam v0.0.0-20230313074753-fafd938c948c h1:Q2Naw
github.com/nuts-foundation/did-ockam v0.0.0-20230313074753-fafd938c948c/go.mod h1:n0NQI71qGVVShnPDjYdCoNStEW0zZoVWbeSQ+esXuhs=
github.com/nuts-foundation/go-did v0.5.1 h1:akc4OzNGHdnliJFoxnnSmUI7j1yKz/BHNi6sXiWX30A=
github.com/nuts-foundation/go-did v0.5.1/go.mod h1:Aw1pIRyB9naQ8z/dc4rrPhn+nHx5LUcGysBGlezL528=
github.com/nuts-foundation/go-leia/v3 v3.3.0 h1:d0AIihk8nF6QCMpA9I01ZS+pp+GCgoJhblTNkyIZz40=
github.com/nuts-foundation/go-leia/v3 v3.3.0/go.mod h1:Md202F2wpwkXGtOUzyqs0p1Y96+wOY8lpmDT9nuaxE0=
github.com/nuts-foundation/go-leia/v4 v4.0.0 h1:/unYCk18qGG2HWcJK4ld4CaM6k7Tdr0bR1vQd1Jwfcg=
github.com/nuts-foundation/go-leia/v4 v4.0.0/go.mod h1:A246dA4nhY99OPCQpG/XbQ/iPyyfSaJchanivuPWpao=
github.com/nuts-foundation/go-stoabs v1.9.0 h1:zK+ugfolaJYyBvGwsRuavLVdycXk4Yw/1gI+tz17lWQ=
github.com/nuts-foundation/go-stoabs v1.9.0/go.mod h1:htbUqSZiaihqAvJfHwtAbQusGaJtIeWpm1pmKjBYXlM=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
Expand Down Expand Up @@ -577,8 +577,8 @@ github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEv
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg=
github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo=
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.0 h1:6aeJ0bzojgWLa82gDQHcx3S0Lr/O51I9bJ5nv6JFx5w=
github.com/tidwall/gjson v1.14.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
Expand Down
272 changes: 272 additions & 0 deletions storage/leia.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
/*
* Copyright (C) 2022 Nuts community
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/

package storage

import (
"context"
"errors"
"fmt"
"github.com/nuts-foundation/go-leia/v4"
"github.com/nuts-foundation/go-stoabs"
"github.com/nuts-foundation/nuts-node/core"
"github.com/nuts-foundation/nuts-node/storage/log"
)

// KVBackedLeiaStore is a wrapper interface for a leia.Store that uses a stoabs.KVStore as backup for any documents added.
type KVBackedLeiaStore interface {
leia.Store
// AddConfiguration adds a configuration for a collection to the store.
// This is needed to know the kind of collection, the backup shelf name and the iterate query to fetch the documents.
AddConfiguration(config LeiaBackupConfiguration)
// HandleRestore migrates the data from the backup store to the leia.Store if needed.
// It's up to the caller to create the indices on the leia.Collections first before calling this method.
HandleRestore() error
}

type kvBackedLeiaStore struct {
store leia.Store
backup stoabs.KVStore
collectionConfigSet map[string]LeiaBackupConfiguration
}

// NewKVBackedLeiaStore creates a wrapper around a leia.Store that uses a stoabs.KVStore as backup. Write operations (add/delete/update) are first performed on the backup store, then on the leia store.
// The backup store is not closed when Close is called. The leia.Store is closed when Close is called.
func NewKVBackedLeiaStore(store leia.Store, backup stoabs.KVStore) (KVBackedLeiaStore, error) {
return &kvBackedLeiaStore{
store: store,
backup: backup,
collectionConfigSet: map[string]LeiaBackupConfiguration{},
}, nil
}

// LeiaBackupConfiguration contains the configuration for a collection that is backed by a stoabs.KVStore.
type LeiaBackupConfiguration struct {
// CollectionName is the name of the collection in the leia.Store.
CollectionName string
CollectionType leia.CollectionType
// BackupShelf is the name of the shelf in the backup store.
BackupShelf string
// SearchQuery is used to fill the backup shelf if not present.
SearchQuery leia.QueryPath
}

type kvBackedCollection struct {
backup stoabs.KVStore
config LeiaBackupConfiguration
underlying leia.Collection
}

func (k *kvBackedLeiaStore) AddConfiguration(config LeiaBackupConfiguration) {
k.collectionConfigSet[config.CollectionName] = config
}

func (k *kvBackedLeiaStore) HandleRestore() error {
// leia indices have been added, so the collection names have been added to the collectionConfigSet.
// Loop over this set to check if the backup store contains any documents for these collections and add them to the leia.Store if the named collection is empty there.
for _, config := range k.collectionConfigSet {
if err := k.handleRestore(config); err != nil {
return fmt.Errorf("error handling restore for collection %s: %w", config.CollectionName, err)
}
}
return nil
}

func (k *kvBackedLeiaStore) handleRestore(config LeiaBackupConfiguration) error {
backupPresent := k.backupStorePresent(config.BackupShelf)
collection := k.store.Collection(config.CollectionType, config.CollectionName)

storePresent := storePresent(collection, config)

if backupPresent && storePresent {
// both are filled => normal operation, done
return nil
}

if !backupPresent && !storePresent {
// both are non-existent => empty node, done
return nil
}

if !storePresent {
log.Logger().
WithField(core.LogFieldStoreShelf, config.BackupShelf).
Info("Missing index for shelf, rebuilding")
// empty node, backup has been restored, refill store
return k.backup.ReadShelf(context.Background(), config.BackupShelf, func(reader stoabs.Reader) error {
return reader.Iterate(func(key stoabs.Key, value []byte) error {
return collection.Add([]leia.Document{value})
}, stoabs.BytesKey{})
})
}

log.Logger().
WithField(core.LogFieldStoreShelf, config.BackupShelf).
Info("Missing store for shelf, creating from index")

// else !backupPresent, process per 100
query := leia.New(leia.NotNil(config.SearchQuery))

const limit = 100
type refDoc struct {
ref leia.Reference
doc leia.Document
}
var set []refDoc
writeDocuments := func(set []refDoc) error {
return k.backup.Write(context.Background(), func(tx stoabs.WriteTx) error {
writer := tx.GetShelfWriter(config.BackupShelf)
for _, entry := range set {
if err := writer.Put(stoabs.BytesKey(entry.ref), entry.doc); err != nil {
return err
}
}
set = make([]refDoc, 0)
return nil
})
}

err := collection.Iterate(query, func(ref leia.Reference, value []byte) error {
set = append(set, refDoc{ref: ref, doc: value})
if len(set) >= limit {
return writeDocuments(set)
}
return nil
})
if err != nil {
return err
}

if len(set) > 0 {
return writeDocuments(set)
}
return nil
}

func (k *kvBackedLeiaStore) Collection(collectionType leia.CollectionType, name string) leia.Collection {
config, ok := k.collectionConfigSet[name]
if !ok {
// we panic here because this is a programming error, not a runtime error
panic("JSON collection not configured")
}
if config.CollectionType != collectionType {
// we panic here because this is a programming error, not a runtime error
panic("Incorrect collection configuration")
}
underlying := kvBackedCollection{
backup: k.backup,
config: config,
underlying: k.store.Collection(collectionType, name),
}
return underlying
}

func (k *kvBackedLeiaStore) Close() error {
return k.store.Close()
}

func (k kvBackedCollection) AddIndex(index ...leia.Index) error {
return k.underlying.AddIndex(index...)
}

func (k kvBackedCollection) DropIndex(name string) error {
return k.underlying.DropIndex(name)
}

func (k kvBackedCollection) NewIndex(name string, parts ...leia.FieldIndexer) leia.Index {
return k.underlying.NewIndex(name, parts...)
}

func (k kvBackedCollection) Add(jsonSet []leia.Document) error {
// first in backup
for _, doc := range jsonSet {
ref := k.Reference(doc)

if err := k.backup.WriteShelf(context.Background(), k.config.BackupShelf, func(writer stoabs.Writer) error {
return writer.Put(stoabs.BytesKey(ref), doc)
}); err != nil {
return err
}
}
// then in index
return k.underlying.Add(jsonSet)
}

func (k kvBackedCollection) Get(ref leia.Reference) (leia.Document, error) {
return k.underlying.Get(ref)
}

func (k kvBackedCollection) Delete(doc leia.Document) error {
// first in backup
ref := k.Reference(doc)

if err := k.backup.WriteShelf(context.Background(), k.config.BackupShelf, func(writer stoabs.Writer) error {
return writer.Put(stoabs.BytesKey(ref), doc)
}); err != nil {
return err
}
// then in index
return k.underlying.Delete(doc)
}

func (k kvBackedCollection) Find(ctx context.Context, query leia.Query) ([]leia.Document, error) {
return k.underlying.Find(ctx, query)
}

func (k kvBackedCollection) Reference(doc leia.Document) leia.Reference {
return k.underlying.Reference(doc)
}

func (k kvBackedCollection) Iterate(query leia.Query, walker leia.DocumentWalker) error {
return k.underlying.Iterate(query, walker)
}

func (k kvBackedCollection) IndexIterate(query leia.Query, fn leia.ReferenceScanFn) error {
return k.underlying.IndexIterate(query, fn)
}

func (k kvBackedCollection) ValuesAtPath(document leia.Document, queryPath leia.QueryPath) ([]leia.Scalar, error) {
return k.underlying.ValuesAtPath(document, queryPath)
}

func (k kvBackedCollection) DocumentCount() (int, error) {
return k.underlying.DocumentCount()
}

func (k *kvBackedLeiaStore) backupStorePresent(backupShelf string) bool {
backupPresent := false

_ = k.backup.ReadShelf(context.Background(), backupShelf, func(reader stoabs.Reader) error {
isEmpty, err := reader.Empty()
backupPresent = !isEmpty
return err
})

return backupPresent
}

func storePresent(collection leia.Collection, config LeiaBackupConfiguration) bool {
issuedPresent := false
query := leia.New(leia.NotNil(config.SearchQuery))
_ = collection.IndexIterate(query, func(key []byte, value []byte) error {
issuedPresent = true
return errors.New("exit")
})

return issuedPresent
}
Loading

0 comments on commit 9275a2a

Please sign in to comment.