diff --git a/core/core.go b/core/core.go index a645c22..2873619 100644 --- a/core/core.go +++ b/core/core.go @@ -47,6 +47,7 @@ type DelegateRequest struct { Uses int Time string + Slot string Users []string Labels []string } @@ -188,7 +189,7 @@ func Init(path string) error { err = fmt.Errorf("failed to load password vault %s: %s", path, err) } - cache = keycache.Cache{UserKeys: make(map[string]keycache.ActiveUser)} + cache = keycache.Cache{UserKeys: make(map[keycache.DelegateIndex]keycache.ActiveUser)} crypt = cryptor.New(&records, &cache) return err @@ -331,7 +332,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.Time); err != nil { + if err = cache.AddKeyFromRecord(pr, s.Name, s.Password, s.Users, s.Labels, s.Uses, s.Slot, s.Time); err != nil { return jsonStatusError(err) } diff --git a/cryptor/cryptor_test.go b/cryptor/cryptor_test.go index ce7a996..4b744ac 100644 --- a/cryptor/cryptor_test.go +++ b/cryptor/cryptor_test.go @@ -108,7 +108,7 @@ func TestDuplicates(t *testing.T) { // Delegate one key at a time and check that decryption fails. for name, pr := range recs { - err = cache.AddKeyFromRecord(pr, name, "weakpassword", nil, nil, 2, "1h") + err = cache.AddKeyFromRecord(pr, name, "weakpassword", nil, nil, 2, "", "1h") if err != nil { t.Fatalf("%v", err) } diff --git a/index.html b/index.html index f0f9c15..c0fb819 100644 --- a/index.html +++ b/index.html @@ -72,6 +72,10 @@ +
+ + +
diff --git a/keycache/keycache.go b/keycache/keycache.go index f262523..2c1082f 100644 --- a/keycache/keycache.go +++ b/keycache/keycache.go @@ -12,6 +12,7 @@ import ( "crypto/rsa" "crypto/sha1" "errors" + "fmt" "log" "time" @@ -19,6 +20,15 @@ import ( "github.com/cloudflare/redoctober/passvault" ) +// DelegateIndex is used to index the map of currently delegated keys. +// This is necessary to provide a way for a delegator to provide multiple +// delegations. It is also used to avoid the complexity of string parsing +// and enforcement of username and slot character requirements. +type DelegateIndex struct { + Name string + Slot string +} + // Usage holds the permissions of a delegated permission type Usage struct { Uses int // Number of uses delegated @@ -37,8 +47,9 @@ type ActiveUser struct { eccKey *ecdsa.PrivateKey } +// Cache represents the current list of delegated keys in memory type Cache struct { - UserKeys map[string]ActiveUser // Decrypted keys in memory, indexed by name. + UserKeys map[DelegateIndex]ActiveUser } // matchesLabel returns true if this usage applies the user and label @@ -77,77 +88,87 @@ func (usage Usage) matches(user string, labels []string) bool { return false } +// NewCache initalizes a new cache. func NewCache() Cache { - return Cache{make(map[string]ActiveUser)} + return Cache{make(map[DelegateIndex]ActiveUser)} } // setUser takes an ActiveUser and adds it to the cache. -func (cache *Cache) setUser(in ActiveUser, name string) { - cache.UserKeys[name] = in +func (cache *Cache) setUser(in ActiveUser, name, slot string) { + cache.UserKeys[DelegateIndex{Name: name, Slot: slot}] = in } // Valid returns true if matching active user is present. func (cache *Cache) Valid(name, user string, labels []string) (present bool) { - key, present := cache.UserKeys[name] - if present { + for d, key := range cache.UserKeys { + if d.Name != name { + continue + } if key.Usage.matches(user, labels) { return true - } else { - present = false } } - return + return false } // matchUser returns the matching active user if present // and a boolean to indicate its presence. -func (cache *Cache) matchUser(name, user string, labels []string) (out ActiveUser, present bool) { - key, present := cache.UserKeys[name] - if present { +func (cache *Cache) matchUser(name, user string, labels []string) (ActiveUser, string, bool) { + var key ActiveUser + for d, key := range cache.UserKeys { + if d.Name != name { + continue + } if key.Usage.matches(user, labels) { - return key, true - } else { - present = false + return key, d.Slot, true } } - return + return key, "", false } // useKey decrements the counter on an active key // for decryption or symmetric encryption -func (cache *Cache) useKey(name, user string, labels []string) { - if val, present := cache.matchUser(name, user, labels); present { +func (cache *Cache) useKey(name, user, slot string, labels []string) { + if val, slot, present := cache.matchUser(name, user, labels); present { val.Usage.Uses -= 1 - cache.setUser(val, name) + cache.setUser(val, name, slot) } } // GetSummary returns the list of active user keys. func (cache *Cache) GetSummary() map[string]ActiveUser { - return cache.UserKeys + summaryData := make(map[string]ActiveUser) + for d, activeUser := range cache.UserKeys { + summaryInfo := d.Name + if d.Slot != "" { + summaryInfo = fmt.Sprintf("%s-%s", d.Name, d.Slot) + } + summaryData[summaryInfo] = activeUser + } + return summaryData } // FlushCache removes all delegated keys. func (cache *Cache) FlushCache() { - for name := range cache.UserKeys { - delete(cache.UserKeys, name) + for d := range cache.UserKeys { + delete(cache.UserKeys, d) } } // Refresh purges all expired or used up keys. func (cache *Cache) Refresh() { - for name, active := range cache.UserKeys { + for d, active := range cache.UserKeys { if active.Usage.Expiry.Before(time.Now()) || active.Usage.Uses <= 0 { - log.Println("Record expired", name, active.Usage.Users, active.Usage.Labels, active.Usage.Expiry) - delete(cache.UserKeys, name) + log.Println("Record expired", d.Name, d.Slot, active.Usage.Users, active.Usage.Labels, active.Usage.Expiry) + delete(cache.UserKeys, d) } } } // AddKeyFromRecord decrypts a key for a given record and adds it to the cache. -func (cache *Cache) AddKeyFromRecord(record passvault.PasswordRecord, name, password string, users, labels []string, uses int, durationString string) (err error) { +func (cache *Cache) AddKeyFromRecord(record passvault.PasswordRecord, name, password string, users, labels []string, uses int, slot, durationString string) (err error) { var current ActiveUser cache.Refresh() @@ -181,7 +202,7 @@ func (cache *Cache) AddKeyFromRecord(record passvault.PasswordRecord, name, pass current.Admin = record.Admin // add current to map (overwriting previous for this name) - cache.setUser(current, name) + cache.setUser(current, name, slot) return } @@ -193,7 +214,7 @@ func (cache *Cache) AddKeyFromRecord(record passvault.PasswordRecord, name, pass func (cache *Cache) DecryptKey(in []byte, name, user string, labels []string, pubEncryptedKey []byte) (out []byte, err error) { cache.Refresh() - decryptKey, ok := cache.matchUser(name, user, labels) + decryptKey, slot, ok := cache.matchUser(name, user, labels) if !ok { return nil, errors.New("Key not delegated") } @@ -227,7 +248,7 @@ func (cache *Cache) DecryptKey(in []byte, name, user string, labels []string, pu out = make([]byte, 16) aesSession.Decrypt(out, in) - cache.useKey(name, user, labels) + cache.useKey(name, user, slot, labels) return } diff --git a/keycache/keycache_test.go b/keycache/keycache_test.go index df7995e..a3b56d3 100644 --- a/keycache/keycache_test.go +++ b/keycache/keycache_test.go @@ -27,7 +27,7 @@ func TestUsesFlush(t *testing.T) { // Initialize keycache and delegate the user's key to it. cache := NewCache() - err = cache.AddKeyFromRecord(pr, "user", "weakpassword", nil, nil, 2, "1h") + err = cache.AddKeyFromRecord(pr, "user", "weakpassword", nil, nil, 2, "", "1h") if err != nil { t.Fatalf("%v", err) } @@ -91,7 +91,7 @@ func TestTimeFlush(t *testing.T) { cache := NewCache() - err = cache.AddKeyFromRecord(pr, "user", "weakpassword", nil, nil, 10, "1s") + err = cache.AddKeyFromRecord(pr, "user", "weakpassword", nil, nil, 10, "", "1s") if err != nil { t.Fatalf("%v", err) } @@ -130,7 +130,7 @@ func TestGoodLabel(t *testing.T) { cache := NewCache() - err = cache.AddKeyFromRecord(pr, "user", "weakpassword", nil, []string{"red"}, 1, "1h") + err = cache.AddKeyFromRecord(pr, "user", "weakpassword", nil, []string{"red"}, 1, "", "1h") if err != nil { t.Fatalf("%v", err) } @@ -172,7 +172,7 @@ func TestBadLabel(t *testing.T) { cache := NewCache() - err = cache.AddKeyFromRecord(pr, "user", "weakpassword", nil, []string{"red"}, 1, "1h") + err = cache.AddKeyFromRecord(pr, "user", "weakpassword", nil, []string{"red"}, 1, "", "1h") if err != nil { t.Fatalf("%v", err) } @@ -218,7 +218,7 @@ func TestGoodUser(t *testing.T) { pr, "user", "weakpassword", []string{"ci", "buildeng", "user"}, []string{"red", "blue"}, - 1, "1h", + 1, "", "1h", ) if err != nil { t.Fatalf("%v", err) @@ -265,7 +265,7 @@ func TestBadUser(t *testing.T) { pr, "user", "weakpassword", []string{"ci", "buildeng", "user"}, []string{"red", "blue"}, - 1, "1h", + 1, "", "1h", ) if err != nil { t.Fatalf("%v", err) diff --git a/redoctober.go b/redoctober.go index e70eb5b..dd6af06 100644 --- a/redoctober.go +++ b/redoctober.go @@ -322,6 +322,10 @@ var indexHtml = []byte(` +
+ + +