Skip to content

Commit

Permalink
Implementation of a file-backed persistence store.
Browse files Browse the repository at this point in the history
This is a rather large change. It consists of the following changes:

+ Direct access to the keycache has been removed from the core
  package. This forces all interaction with the cache to go
  through the Cryptor, which is required for persistence. The
  Cryptor needs to know when the cache has changed, and the only
  way to do this effectively is to make the Cryptor responsible
  for managing the keycache.

+ A new persist package has been added. This provides a Store
  interface, for which two implementations are provided. The
  first is a null persister: this is used when no persistence
  is configured. The second is a file-backed persistence store.

+ The Cryptor now persists the cache every time it changes.

Additionally, a number of missing returns in a function in the core
package have been added.
  • Loading branch information
kisom committed Aug 2, 2016
1 parent c230e7a commit b37a19d
Show file tree
Hide file tree
Showing 13 changed files with 1,037 additions and 37 deletions.
14 changes: 12 additions & 2 deletions config/config.go
Expand Up @@ -77,8 +77,18 @@ type Delegations struct {
Persist bool `json:"persist"`

// Policy contains the MSP predicate for delegation
// persistence.
Policy string `json:"policy"`
// persistence, and users contains the users allowed
// to delegate.
Policy string `json:"policy"`
Users []string `json:"users"`

// Mechanism specifies the persistence mechanism to use.
Mechanism string `json:"mechanism"`

// Location contains location information for the persistence
// mechanism, such as a file path or database connection
// string.
Location string `json:"location"`
}

// Config contains all the configuration options for a redoctober
Expand Down
41 changes: 26 additions & 15 deletions core/core.go
Expand Up @@ -21,9 +21,8 @@ import (
)

var (
crypt cryptor.Cryptor
crypt *cryptor.Cryptor
records passvault.Records
cache keycache.Cache
orders order.Orderer
)

Expand Down Expand Up @@ -199,7 +198,7 @@ func jsonStatusError(err error) ([]byte, error) {
return json.Marshal(ResponseData{Status: err.Error()})
}
func jsonSummary() ([]byte, error) {
return json.Marshal(SummaryData{Status: "ok", Live: cache.GetSummary(), All: records.GetSummary()})
return json.Marshal(SummaryData{Status: "ok", Live: crypt.LiveSummary(), All: records.GetSummary()})
}
func jsonResponse(resp []byte) ([]byte, error) {
return json.Marshal(ResponseData{Status: "ok", Response: resp})
Expand Down Expand Up @@ -276,8 +275,7 @@ func Init(path string, config *config.Config) error {
restore.State = PDStateNeverPersist

orders = order.NewOrderer(hipchatClient)
cache = keycache.Cache{UserKeys: make(map[keycache.DelegateIndex]keycache.ActiveUser)}
crypt = cryptor.New(&records, &cache)
crypt, err = cryptor.New(&records, nil, config)

return err
}
Expand Down Expand Up @@ -320,7 +318,6 @@ func Create(jsonIn []byte) ([]byte, error) {
func Summary(jsonIn []byte) ([]byte, error) {
var s SummaryRequest
var err error
cache.Refresh()

defer func() {
if err != nil {
Expand All @@ -330,6 +327,11 @@ func Summary(jsonIn []byte) ([]byte, error) {
}
}()

err = crypt.Refresh()
if err != nil {
return jsonStatusError(err)
}

if err := json.Unmarshal(jsonIn, &s); err != nil {
return jsonStatusError(err)
}
Expand Down Expand Up @@ -373,7 +375,11 @@ func Purge(jsonIn []byte) ([]byte, error) {
return jsonStatusError(err)
}

cache.FlushCache()
err = crypt.Flush()
if err != nil {
return jsonStatusError(err)
}

return jsonStatusOk()
}

Expand Down Expand Up @@ -426,7 +432,7 @@ func Delegate(jsonIn []byte) ([]byte, error) {
}

// add signed-in record to active set
if err = cache.AddKeyFromRecord(pr, s.Name, s.Password, s.Users, s.Labels, s.Uses, s.Slot, s.Time); err != nil {
if err = crypt.Delegate(pr, s.Name, s.Password, s.Users, s.Labels, s.Uses, s.Slot, s.Time); err != nil {
return jsonStatusError(err)
}

Expand Down Expand Up @@ -798,27 +804,32 @@ func Order(jsonIn []byte) (out []byte, err error) {
// Get the owners of the ciphertext.
owners, _, err := crypt.GetOwners(o.EncryptedData)
if err != nil {
jsonStatusError(err)
return jsonStatusError(err)
}
if o.Duration == "" {
err = errors.New("Duration required when placing an order.")
jsonStatusError(err)
return jsonStatusError(err)
}
if o.Uses == 0 {
err = errors.New("Number of required uses necessary when placing an order.")
jsonStatusError(err)
return jsonStatusError(err)
}
cache.Refresh()

err = crypt.Refresh()
if err != nil {
return jsonStatusError(err)
}

orderNum := order.GenerateNum()

if len(o.Users) == 0 {
err = errors.New("Must specify at least one user per order.")
jsonStatusError(err)
return jsonStatusError(err)
}
adminsDelegated, numDelegated := cache.DelegateStatus(o.Users[0], o.Labels, owners)
adminsDelegated, numDelegated := crypt.DelegateStatus(o.Users[0], o.Labels, owners)
duration, err := time.ParseDuration(o.Duration)
if err != nil {
jsonStatusError(err)
return jsonStatusError(err)
}
currentTime := time.Now()
ord := order.CreateOrder(o.Name,
Expand Down
32 changes: 23 additions & 9 deletions core/core_test.go
Expand Up @@ -174,7 +174,7 @@ func TestSummary(t *testing.T) {

dataLive, ok := s.Live["Bob"]
if !ok {
t.Fatalf("Error in summary of account, record missing, %v", cache.UserKeys)
t.Fatalf("Error in summary of account, record missing, %v", crypt.LiveSummary())
}
if dataLive.Admin != false {
t.Fatalf("Error in summary of account, record missing")
Expand All @@ -184,7 +184,7 @@ func TestSummary(t *testing.T) {
}

var s1 SummaryData
delegations := cache.GetSummary()
delegations := crypt.LiveSummary()
if len(delegations) == 0 {
t.Fatal("no delegations active")
}
Expand Down Expand Up @@ -230,7 +230,7 @@ func TestSummary(t *testing.T) {
t.Fatal("Bob was removed from the list of users")
}

delegations = cache.GetSummary()
delegations = crypt.LiveSummary()
if len(delegations) != 0 {
t.Fatalf("purge failed to clear delegations (%d delegations remain)", len(delegations))
}
Expand Down Expand Up @@ -470,7 +470,11 @@ func TestEncryptDecrypt(t *testing.T) {
}

// check summary to see if none are delegated
cache.Refresh()
err = crypt.Refresh()
if err != nil {
t.Fatalf("Error in summary: %s", err)
}

respJson, err = Summary(summaryJson)
if err != nil {
t.Fatalf("Error in summary, %v", err)
Expand Down Expand Up @@ -557,7 +561,11 @@ func TestEncryptDecrypt(t *testing.T) {
}

// verify the presence of the two delgations
cache.Refresh()
err = crypt.Refresh()
if err != nil {
t.Fatalf("Error in summary: %s", err)
}

var sum2 SummaryData
respJson, err = Summary(summaryJson)
if err != nil {
Expand Down Expand Up @@ -936,7 +944,11 @@ func TestModify(t *testing.T) {
}

// check summary to see if none are delegated
cache.Refresh()
err = crypt.Refresh()
if err != nil {
t.Fatalf("Error refreshing: %s", err)
}

respJson, err = Summary(summaryJson)
if err != nil {
t.Fatalf("Error in summary, %v", err)
Expand Down Expand Up @@ -1078,6 +1090,7 @@ func TestStatic(t *testing.T) {
if err != nil {
t.Fatalf("Error opening file, %v", err)
}
defer os.Remove("/tmp/db1.json")

_, err = file.Write(diskVault)
if err != nil {
Expand Down Expand Up @@ -1139,9 +1152,10 @@ func TestStatic(t *testing.T) {
t.Fatalf("Error in summary, %v, %v", expected, r.Response)
}

cache.FlushCache()

os.Remove("/tmp/db1.json")
err = crypt.Flush()
if err != nil {
t.Fatalf("Error flushing cache: %s", err)
}
}

func TestValidateName(t *testing.T) {
Expand Down
132 changes: 129 additions & 3 deletions cryptor/cryptor.go
Expand Up @@ -15,10 +15,12 @@ import (
"sort"
"strconv"

"github.com/cloudflare/redoctober/config"
"github.com/cloudflare/redoctober/keycache"
"github.com/cloudflare/redoctober/msp"
"github.com/cloudflare/redoctober/padding"
"github.com/cloudflare/redoctober/passvault"
"github.com/cloudflare/redoctober/persist"
"github.com/cloudflare/redoctober/symcrypt"
)

Expand All @@ -29,10 +31,25 @@ const (
type Cryptor struct {
records *passvault.Records
cache *keycache.Cache
persist persist.Store
}

func New(records *passvault.Records, cache *keycache.Cache) Cryptor {
return Cryptor{records, cache}
func New(records *passvault.Records, cache *keycache.Cache, config *config.Config) (*Cryptor, error) {
if cache == nil {
cache = &keycache.Cache{UserKeys: make(map[keycache.DelegateIndex]keycache.ActiveUser)}
}

store, err := persist.New(config.Delegations)
if err != nil {
return nil, err
}

c := &Cryptor{
records: records,
cache: cache,
persist: store,
}
return c, nil
}

// AccessStructure represents different possible access structures for
Expand Down Expand Up @@ -525,6 +542,10 @@ func (c *Cryptor) Encrypt(in []byte, labels []string, access AccessStructure) (r

// Decrypt decrypts a file using the keys in the key cache.
func (c *Cryptor) Decrypt(in []byte, user string) (resp []byte, labels, names []string, secure bool, err error) {
return c.decrypt(c.cache, in, user)
}

func (c *Cryptor) decrypt(cache *keycache.Cache, in []byte, user string) (resp []byte, labels, names []string, secure bool, err error) {
// unwrap encrypted file
var encrypted EncryptedData
if err = json.Unmarshal(in, &encrypted); err != nil {
Expand Down Expand Up @@ -563,7 +584,7 @@ func (c *Cryptor) Decrypt(in []byte, user string) (resp []byte, labels, names []

// decrypt file key with delegate keys
var unwrappedKey = make([]byte, 16)
unwrappedKey, names, err = encrypted.unwrapKey(c.cache, user)
unwrappedKey, names, err = encrypted.unwrapKey(cache, user)
if err != nil {
return
}
Expand Down Expand Up @@ -642,3 +663,108 @@ func (c *Cryptor) GetOwners(in []byte) (names []string, predicate string, err er

return
}

// LiveSummary returns a list of the users currently delegated.
func (c *Cryptor) LiveSummary() map[string]keycache.ActiveUser {
return c.cache.GetSummary()
}

// Refresh purges all expired or fully-used delegations in the
// crypto's key cache. It returns the number of delegations that
// were removed.
func (c *Cryptor) Refresh() error {
n := c.cache.Refresh()
if n != 0 {
return c.store()
}
return nil
}

// Flush removes all delegations.
func (c *Cryptor) Flush() error {
if c.cache.Flush() {
return c.store()
}
return nil
}

// Delegate attempts to decrypt a key for the specified user and add
// the key to the key cache.
func (c *Cryptor) Delegate(record passvault.PasswordRecord, name, password string, users, labels []string, uses int, slot, durationString string) (err error) {
err = c.cache.AddKeyFromRecord(record, name, password, users, labels, uses, slot, durationString)
if err != nil {
return err
}

return c.store()
}

// DelegateStatus will return a list of admins who have delegated to a particular user, for a particular label.
// This is useful information to have when determining the status of an order and conveying order progress.
func (c *Cryptor) DelegateStatus(name string, labels, admins []string) (adminsDelegated []string, hasDelegated int) {
return c.cache.DelegateStatus(name, labels, admins)
}

var (
persistLabels = []string{"restore"}
persistUsers = []string{"restore"}
)

// store serialises the key cache, encrypts it, and writes it to disk.
func (c *Cryptor) store() error {
st := c.persist.Status()
if st.State != persist.NowPersisting {
return nil
}

cache, err := json.Marshal(c.cache.GetSummary())
if err != nil {
return err
}

access := AccessStructure{
Names: persistUsers,
Predicate: c.persist.Policy(),
}

cache, err = c.Encrypt(cache, persistLabels, access)
if err != nil {
return err
}

return c.persist.Store(cache)
}

// ErrRestoreDelegations is a sentinal value returned when more
// delegations are needed for the restore to continue.
var ErrRestoreDelegations = errors.New("cryptor: need more delegations")

func (c *Cryptor) Restore(name, password string, uses int, slot, durationString string) error {
record, ok := c.records.GetRecord(name)
if !ok {
return errors.New("Missing user on disk")
}

err := c.persist.Delegate(record, name, password, c.persist.Users(), persistLabels, uses, slot, durationString)
if err != nil {
return err
}

// A failure to decrypt isn't an error, it just means there
// aren't enough delegations yet; the sentinal value
// ErrRestoreDelegations is returned to indicate this.
cache, _, _, _, err := c.decrypt(c.persist.Cache(), c.persist.Blob(), name)
if err != nil {
return ErrRestoreDelegations
}

var uk map[string]keycache.ActiveUser
err = json.Unmarshal(cache, &uk)
if err != nil {
return err
}

c.cache = keycache.NewFrom(uk)
c.persist.Persist()
return nil
}

0 comments on commit b37a19d

Please sign in to comment.