Merge pull request #102 from ejcx/multiDel

Multiple delegations to redoctober
This commit is contained in:
Kyle Isom
2015-11-11 08:28:35 -08:00
6 changed files with 68 additions and 38 deletions

View File

@@ -47,6 +47,7 @@ type DelegateRequest struct {
Uses int
Time string
Slot string
Users []string
Labels []string
}
@@ -194,7 +195,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
@@ -344,7 +345,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)
}

View File

@@ -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)
}

View File

@@ -72,6 +72,10 @@
<label for="delegate-labels">Labels to allow <small>(comma separated)</small></label>
<input type="text" name="Labels" class="form-control" id="delegate-labels" placeholder="e.g. Blue, Red" />
</div>
<div class="col-md-6">
<label for="delegate-labels">Slot Name</label>
<input type="text" name="Slot" class="form-control" id="delegate-slot" placeholder="Afternoon" />
</div>
</div>
<button type="submit" class="btn btn-primary">Delegate</button>
</form>

View File

@@ -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
}

View File

@@ -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)

View File

@@ -343,6 +343,10 @@ var indexHtml = []byte(`<!DOCTYPE html>
<label for="delegate-labels">Labels to allow <small>(comma separated)</small></label>
<input type="text" name="Labels" class="form-control" id="delegate-labels" placeholder="e.g. Blue, Red" />
</div>
<div class="col-md-6">
<label for="delegate-labels">Slot Name</label>
<input type="text" name="Slot" class="form-control" id="delegate-slot" placeholder="Afternoon" />
</div>
</div>
<button type="submit" class="btn btn-primary">Delegate</button>
</form>