Skip to content
This repository has been archived by the owner on May 28, 2024. It is now read-only.

Commit

Permalink
Merge pull request #25 from findy-network/enclave-backup
Browse files Browse the repository at this point in the history
Enclave backup
  • Loading branch information
techlab-lainio authored Feb 11, 2021
2 parents 80251d5 + 20f2b61 commit 808c6e0
Show file tree
Hide file tree
Showing 12 changed files with 470 additions and 88 deletions.
40 changes: 40 additions & 0 deletions backup/backup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Package backup implements dedicated helpers for the current backup system.
*/
package backup

import (
"io"
"os"
"path/filepath"

"github.com/golang/glog"
"github.com/lainio/err2"
)

// PrefixName builds a new prefixed file name. The file name is in format:
// prefix_file.name
// If prefix is empty the returned string starts with _.
func PrefixName(prefix, name string) string {
dir, file := filepath.Split(name)
file = prefix + "_" + file
backupName := filepath.Join(dir, file)
glog.V(3).Infoln("backup name:", backupName)
return backupName
}

// FileCopy copies a source file to destination file.
func FileCopy(src, dst string) (err error) {
defer err2.Returnf(&err, "copy %s -> %s", src, dst)

r := err2.File.Try(os.Open(src))
defer r.Close()

w := err2.File.Try(os.Create(dst))
defer err2.Handle(&err, func() {
os.Remove(dst)
})
defer w.Close()
err2.Empty.Try(io.Copy(w, r))
return nil
}
36 changes: 36 additions & 0 deletions backup/backup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package backup

import "testing"

func TestMgd_prefixName(t *testing.T) {
tests := []struct {
name string
prefix string
backupName string
want string
}{
{name: "path include",
prefix: "XXXXX",
backupName: "/file/path/backup.bolt",
want: "/file/path/XXXXX_backup.bolt"},
{name: "no path",
prefix: "XXXXX",
backupName: "backup.bolt",
want: "XXXXX_backup.bolt"},
{name: "no path no prefix",
prefix: "XXXXX",
backupName: "backup.bolt",
want: "XXXXX_backup.bolt"},
{name: "path include no prefix",
prefix: "",
backupName: "/file/path/backup.bolt",
want: "/file/path/_backup.bolt"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := PrefixName(tt.prefix, tt.backupName); got != tt.want {
t.Errorf("backupName() = %v, want %v", got, tt.want)
}
})
}
}
211 changes: 165 additions & 46 deletions crypto/db/db.go
Original file line number Diff line number Diff line change
@@ -1,37 +1,66 @@
package db

import (
"errors"
"fmt"
"os"
"sync"
"time"

"github.com/findy-network/findy-grpc/backup"
"github.com/golang/glog"
"github.com/lainio/err2"
bolt "go.etcd.io/bbolt"
)

var DB *bolt.DB
type Cfg struct {
Filename string
BackupName string
Buckets [][]byte
}

type Mgd struct {
Cfg
db *bolt.DB
dirty bool
l sync.Mutex
}

// ErrSealBoxAlreadyExists is an error for enclave sealed box already exists.
var ErrSealBoxAlreadyExists = errors.New("enclave sealed box exists")
func (m *Mgd) operate(f func(db *bolt.DB) error) (err error) {
defer err2.Annotate("db operate", &err)

func assertDB() {
if DB == nil {
panic("don't forget init the seal box")
m.l.Lock()
defer m.l.Unlock()

m.dirty = true
if m.db == nil {
err2.Check(m.open())
}
return f(mgedDB.db)
}

func Open(filename string, buckets [][]byte) (err error) {
if DB != nil {
return ErrSealBoxAlreadyExists
var (
mgedDB Mgd
)

// Init initializes managed version of the encrypted database. Database is ready
// to use after this call. See more information of Cfg struct.
func Init(cfg Cfg) (err error) {
mgedDB = Mgd{
Cfg: cfg,
}
return nil
}

func (m *Mgd) open() (err error) {
defer err2.Return(&err)

DB, err = bolt.Open(filename, 0600, nil)
glog.V(1).Infoln("open DB", m.Filename)
m.db, err = bolt.Open(m.Filename, 0600, nil)
err2.Check(err)

err2.Check(DB.Update(func(tx *bolt.Tx) (err error) {
err2.Check(m.db.Update(func(tx *bolt.Tx) (err error) {
defer err2.Annotate("create buckets", &err)

for _, bucket := range buckets {
for _, bucket := range m.Buckets {
err2.Try(tx.CreateBucketIfNotExists(bucket))
}
return nil
Expand All @@ -42,6 +71,8 @@ func Open(filename string, buckets [][]byte) (err error) {
type Filter func(value []byte) (k []byte)
type Use func(value []byte) interface{}

// Data is general data element for encrypted database. It offers placeholders
// for read, write, and use operators to over write.
type Data struct {
Data []byte
Read Filter
Expand Down Expand Up @@ -72,50 +103,138 @@ func (d *Data) set(b []byte) {
}
}

// Close closes the sealed box of the enclave. It can be open again with
// InitSealedBox.
func Close() {
defer err2.CatchTrace(func(err error) {
fmt.Println(err)
})
assertDB()
// close closes managed encrypted db. Note! Instance must be locked!
func (m *Mgd) close() (err error) {
defer err2.Return(&err)

glog.V(1).Infoln("close DB", m.Filename)
err2.Check(m.db.Close())
m.db = nil
return nil
}

err2.Check(DB.Close())
DB = nil
func (m *Mgd) backupName() string {
timeStr := time.Now().Format(time.RFC3339)
return backup.PrefixName(timeStr, m.BackupName)
}

// AddKeyValueToBucket add value to bucket pointed by the index. keyValue and
// index use Data types operators to encrypt and hash data on the fly.
func AddKeyValueToBucket(bucket []byte, keyValue, index *Data) (err error) {
assertDB()

defer err2.Annotate("add key", &err)
return mgedDB.operate(func(DB *bolt.DB) error {
defer err2.Annotate("add key", &err)

err2.Check(DB.Update(func(tx *bolt.Tx) (err error) {
defer err2.Return(&err)
err2.Check(DB.Update(func(tx *bolt.Tx) (err error) {
defer err2.Return(&err)

b := tx.Bucket(bucket)
err2.Check(b.Put(index.get(), keyValue.get()))
b := tx.Bucket(bucket)
err2.Check(b.Put(index.get(), keyValue.get()))
return nil
}))
return nil
}))
return nil
})
}

// GetKeyValueFromBucket writes keyValue data by the index from a bucket. It
// returns found if key value exists. Returns are only if it cannot perform the
// transaction successfully.
func GetKeyValueFromBucket(bucket []byte, index, keyValue *Data) (found bool, err error) {
assertDB()

defer err2.Return(&err)

err2.Check(DB.View(func(tx *bolt.Tx) (err error) {
defer err2.Return(&err)

b := tx.Bucket(bucket)
d := b.Get(index.get())
if d == nil {
found = false
defer err2.Annotate("get value", &err)

err2.Check(mgedDB.operate(func(DB *bolt.DB) error {
err2.Check(DB.View(func(tx *bolt.Tx) (err error) {
defer err2.Return(&err)

b := tx.Bucket(bucket)
d := b.Get(index.get())
if d == nil {
found = false
return nil
}
keyValue.set(d)
found = true
return nil
}
keyValue.set(d)
found = true
}))
return nil
}))
return found, nil
}

// BackupTicker creates a backup ticker which takes backup copy of the database
// file specified by the interval. Ticker can be stopped with returned done
// channel.
func BackupTicker(interval time.Duration) (done chan<- struct{}) {
ticker := time.NewTicker(interval)
doneCh := make(chan struct{})
go func() {
defer err2.CatchTrace(func(err error) {
glog.Error(err)
})
for {
select {
case <-doneCh:
return
case <-ticker.C:
_, err := Backup()
if err != nil {
glog.Errorln("backup ticker:", err)
}
}
}
}()
return doneCh
}

// Backup takes backup copy of the database. Before backup the database is
// closed.
func Backup() (did bool, err error) {
defer err2.Annotate("backup", &err)

mgedDB.l.Lock()
defer mgedDB.l.Unlock()

if !mgedDB.dirty {
glog.V(1).Infoln("db isn't dirty, skipping backup")
return false, nil
}
if mgedDB.db != nil {
err2.Check(mgedDB.close())
}

// we keep locks on during the whole copy, but try to do it as fast as
// possible. If this would be critical we could first read the source file
// when locks are on and then write the target file in a new gorountine.
backupName := mgedDB.backupName()
err2.Check(backup.FileCopy(mgedDB.Filename, backupName))
glog.V(1).Infoln("successful backup to file:", backupName)

mgedDB.dirty = false
return true, nil
}

// Wipe removes the whole database and its master file.
func Wipe() (err error) {
defer err2.Annotate("wipe", &err)

mgedDB.l.Lock()
defer mgedDB.l.Unlock()

if mgedDB.db != nil {
err2.Check(mgedDB.close())
}

return os.RemoveAll(mgedDB.Filename)
}

// Close closes the database. It can be used after that if wanted. Transactions
// opens the database when needed.
func Close() (err error) {
mgedDB.l.Lock()
defer mgedDB.l.Unlock()

if mgedDB.db != nil {
return mgedDB.close()
}

return nil
}
Loading

0 comments on commit 808c6e0

Please sign in to comment.