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(`
+
+
+
+