"x/ref": Add Dump method to BlessingRoots
This CL adds a 'Dump' method to BlessingRoots to obtain
the set of all roots. While I hope that this is useful
in general, it is necessary for addressing:
https://github.com/vanadium/issues/issues/228
MultiPart: 2/2
Change-Id: I705b312aabc0a4747f14728fba360fb9cec683db
diff --git a/services/agent/agentlib/agent_test.go b/services/agent/agentlib/agent_test.go
index 9f3eff7..1a9064a 100644
--- a/services/agent/agentlib/agent_test.go
+++ b/services/agent/agentlib/agent_test.go
@@ -25,6 +25,22 @@
_ "v.io/x/ref/profiles"
)
+// As of April 28, 2015, the benchmarks for serving a principal with and
+// without the agent are as follows:
+//
+// BenchmarkSignNoAgent : 889608 ns/op
+// BenchmarkSignCachedAgent : 6961410 ns/op
+// BenchmarkSignUncachedAgent : 7403763 ns/op
+// BenchmarkDefaultNoAgent : 139 ns/op
+// BenchmarkDefaultCachedAgent : 41 ns/op
+// BenchmarkDefaultUncachedAgent : 9732978 ns/op
+// BenchmarkRecognizedNegativeNoAgent : 34859 ns/op
+// BenchmarkRecognizedNegativeCachedAgent : 31043 ns/op
+// BenchmarkRecognizedNegativeUncachedAgent: 5110308 ns/op
+// BenchmarkRecognizedNoAgent : 13457 ns/op
+// BenchmarkRecognizedCachedAgent : 12609 ns/op
+// BenchmarkRecognizedUncachedAgent : 4232959 ns/op
+
//go:generate v23 test generate
func getPrincipalAndHang(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
diff --git a/services/agent/agentlib/client.go b/services/agent/agentlib/client.go
index bcda630..7dc2c3c 100644
--- a/services/agent/agentlib/client.go
+++ b/services/agent/agentlib/client.go
@@ -272,6 +272,26 @@
return b.caller.call("BlessingRootsRecognized", results(), marshalledKey, blessing)
}
+func (b *blessingRoots) Dump() map[security.BlessingPattern][]security.PublicKey {
+ var marshaledRoots map[security.BlessingPattern][][]byte
+ if err := b.caller.call("BlessingRootsDump", results(&marshaledRoots)); err != nil {
+ vlog.Errorf("error calling BlessingRootsDump: %v", err)
+ return nil
+ }
+ ret := make(map[security.BlessingPattern][]security.PublicKey)
+ for p, marshaledKeys := range marshaledRoots {
+ for _, marshaledKey := range marshaledKeys {
+ key, err := security.UnmarshalPublicKey(marshaledKey)
+ if err != nil {
+ vlog.Errorf("security.UnmarshalPublicKey(%v) returned error: %v", marshaledKey, err)
+ continue
+ }
+ ret[p] = append(ret[p], key)
+ }
+ }
+ return ret
+}
+
func (b *blessingRoots) DebugString() (s string) {
err := b.caller.call("BlessingRootsDebugString", results(&s))
if err != nil {
diff --git a/services/agent/internal/cache/cache.go b/services/agent/internal/cache/cache.go
index e16ca7a..252b6a3 100644
--- a/services/agent/internal/cache/cache.go
+++ b/services/agent/internal/cache/cache.go
@@ -5,7 +5,6 @@
package cache
import (
- "encoding/base64"
"fmt"
"sync"
@@ -29,26 +28,24 @@
// cachedRoots is a security.BlessingRoots implementation that
// wraps over another implementation and adds caching.
-// It caches both known roots, and a blessings known to be untrusted.
-// Only the negative cache is cleared when flushing, since BlessingRoots
-// doesn't allow removal.
type cachedRoots struct {
- mu *sync.RWMutex
- impl security.BlessingRoots
- cache map[string][]security.BlessingPattern
- negative *lru.Cache // key + blessing -> error
+ mu *sync.RWMutex
+ impl security.BlessingRoots
+ cache map[string][]security.BlessingPattern // GUARDED_BY(mu)
+
+ // TODO(ataly): Get rid of the following fields once all agents have been
+ // updated to support the 'Dump' method.
+ dumpExists bool // GUARDED_BY(my)
+ negative *lru.Cache // key + blessing -> error
}
-func newCachedRoots(impl security.BlessingRoots, mu *sync.RWMutex) *cachedRoots {
+func newCachedRoots(impl security.BlessingRoots, mu *sync.RWMutex) (*cachedRoots, error) {
roots := &cachedRoots{mu: mu, impl: impl}
roots.flush()
- return roots
-}
-
-// Must be called while holding mu.
-func (r *cachedRoots) flush() {
- r.negative = lru.New(maxNegativeCacheEntries)
- r.cache = make(map[string][]security.BlessingPattern)
+ if err := roots.fetchAndCacheRoots(); err != nil {
+ return nil, err
+ }
+ return roots, nil
}
func (r *cachedRoots) Add(root security.PublicKey, pattern security.BlessingPattern) error {
@@ -60,19 +57,15 @@
defer r.mu.Unlock()
r.mu.Lock()
- err = r.impl.Add(root, pattern)
- if err == nil {
+ if err := r.impl.Add(root, pattern); err != nil {
+ return err
+ }
+
+ if r.cache != nil {
+
r.cache[cacheKey] = append(r.cache[cacheKey], pattern)
}
- return err
-}
-
-func keyToString(key security.PublicKey) (string, error) {
- bytes, err := key.MarshalBinary()
- if err != nil {
- return "", err
- }
- return base64.StdEncoding.EncodeToString(bytes), nil
+ return nil
}
func (r *cachedRoots) Recognized(root security.PublicKey, blessing string) (result error) {
@@ -82,41 +75,144 @@
}
r.mu.RLock()
+ var cacheExists bool
+ if r.cache != nil {
+ err = r.recognizeFromCache(key, root, blessing)
+ cacheExists = true
+ }
+ r.mu.RUnlock()
+ if !cacheExists {
+ r.mu.Lock()
+ if err := r.fetchAndCacheRoots(); err != nil {
+ r.mu.Unlock()
+ return err
+ }
+ err = r.recognizeFromCache(key, root, blessing)
+ r.mu.Unlock()
+ }
+
+ // TODO(ataly): Get rid of the following block once all agents have been updated
+ // to support the 'Dump' method.
+ r.mu.RLock()
+ if !r.dumpExists && err != nil {
+ negKey := key + blessing
+ negErr, ok := r.negative.Get(negKey)
+ if !ok {
+ r.mu.RUnlock()
+ return r.recognizeFromImpl(key, root, blessing)
+ }
+ r.negative.Put(negKey, err)
+ err = negErr.(error)
+ }
+ r.mu.RUnlock()
+
+ return err
+}
+
+func (r *cachedRoots) Dump() map[security.BlessingPattern][]security.PublicKey {
+ var (
+ cacheExists bool
+ dump map[security.BlessingPattern][]security.PublicKey
+ )
+
+ r.mu.RLock()
+ if r.cache != nil {
+ cacheExists = true
+ dump = r.dumpFromCache()
+ }
+ r.mu.RUnlock()
+
+ if !cacheExists {
+ r.mu.Lock()
+ if err := r.fetchAndCacheRoots(); err != nil {
+ vlog.Errorf("failed to cache roots: %v", err)
+ r.mu.Unlock()
+ return nil
+ }
+ dump = r.dumpFromCache()
+ r.mu.Unlock()
+ }
+ return dump
+}
+
+func (r *cachedRoots) DebugString() string {
+ return r.impl.DebugString()
+}
+
+// Must be called while holding mu.
+func (r *cachedRoots) fetchAndCacheRoots() error {
+ dump := r.impl.Dump()
+ r.cache = make(map[string][]security.BlessingPattern)
+ if dump == nil {
+ r.dumpExists = false
+ return nil
+ }
+
+ for p, keys := range dump {
+ for _, key := range keys {
+ cacheKey, err := keyToString(key)
+ if err != nil {
+ return err
+ }
+ r.cache[cacheKey] = append(r.cache[cacheKey], p)
+ }
+ }
+ r.dumpExists = true
+ return nil
+}
+
+// Must be called while holding mu.
+func (r *cachedRoots) flush() {
+ r.cache = nil
+ r.negative = lru.New(maxNegativeCacheEntries)
+}
+
+// Must be called while holding mu.
+func (r *cachedRoots) dumpFromCache() map[security.BlessingPattern][]security.PublicKey {
+ if !r.dumpExists {
+ return nil
+ }
+ dump := make(map[security.BlessingPattern][]security.PublicKey)
+ for keyStr, patterns := range r.cache {
+ key, err := security.UnmarshalPublicKey([]byte(keyStr))
+ if err != nil {
+ vlog.Errorf("security.UnmarshalPublicKey(%v) returned error: %v", []byte(keyStr), err)
+ return nil
+ }
+ for _, p := range patterns {
+ dump[p] = append(dump[p], key)
+ }
+ }
+ return dump
+}
+
+// Must be called while holding mu.
+func (r *cachedRoots) recognizeFromCache(key string, root security.PublicKey, blessing string) error {
for _, p := range r.cache[key] {
if p.MatchedBy(blessing) {
- r.mu.RUnlock()
return nil
}
}
- r.mu.RUnlock()
- r.mu.Lock()
- negKey := key + blessing
- if err, ok := r.negative.Get(negKey); ok {
- r.negative.Put(negKey, err)
- r.mu.Unlock()
- return err.(error)
- }
- r.mu.Unlock()
- return r.recognizeAndCache(key, root, blessing)
+ return security.NewErrUnrecognizedRoot(nil, root.String(), nil)
}
-func (r *cachedRoots) recognizeAndCache(key string, root security.PublicKey, blessing string) error {
+// TODO(ataly): Get rid of this method once all agents have been updated
+// to support the 'Dump' method.
+func (r *cachedRoots) recognizeFromImpl(key string, root security.PublicKey, blessing string) error {
+ negKey := key + blessing
err := r.impl.Recognized(root, blessing)
+
r.mu.Lock()
if err == nil {
r.cache[key] = append(r.cache[key], security.BlessingPattern(blessing))
} else {
- r.negative.Put(key+blessing, err)
+ r.negative.Put(negKey, err)
}
r.mu.Unlock()
return err
}
-func (r cachedRoots) DebugString() string {
- return r.impl.DebugString()
-}
-
// cachedStore is a security.BlessingStore implementation that
// wraps over another implementation and adds caching.
type cachedStore struct {
@@ -216,7 +312,7 @@
return fmt.Sprintf("cached[%s]", s.impl)
}
-// Must be called while holding mu
+// Must be called while holding mu.
func (s *cachedStore) flush() {
s.hasDef = false
s.peers = nil
@@ -264,7 +360,10 @@
func NewCachedPrincipal(ctx *context.T, impl security.Principal, call rpc.ClientCall) (p security.Principal, err error) {
var mu sync.RWMutex
- cachedRoots := newCachedRoots(impl.Roots(), &mu)
+ cachedRoots, err := newCachedRoots(impl.Roots(), &mu)
+ if err != nil {
+ return
+ }
cachedStore := &cachedStore{mu: &mu, key: impl.PublicKey(), impl: impl.BlessingStore()}
flush := func() {
defer mu.Unlock()
@@ -293,3 +392,11 @@
p = &cachedPrincipal{p, impl}
return
}
+
+func keyToString(key security.PublicKey) (string, error) {
+ bytes, err := key.MarshalBinary()
+ if err != nil {
+ return "", err
+ }
+ return string(bytes), nil
+}
diff --git a/services/agent/internal/cache/cache_test.go b/services/agent/internal/cache/cache_test.go
index 852d0ce..cd89a5d0 100644
--- a/services/agent/internal/cache/cache_test.go
+++ b/services/agent/internal/cache/cache_test.go
@@ -17,7 +17,11 @@
var mu sync.RWMutex
p := testutil.NewPrincipal()
impl := p.Roots()
- return p.PublicKey(), impl, newCachedRoots(impl, &mu)
+ roots, err := newCachedRoots(impl, &mu)
+ if err != nil {
+ panic(err)
+ }
+ return p.PublicKey(), impl, roots
}
func TestCreateRoots(t *testing.T) {
@@ -33,7 +37,7 @@
func expectRecognized(roots security.BlessingRoots, key security.PublicKey, blessing string) string {
err := roots.Recognized(key, blessing)
if err != nil {
- return fmt.Sprintf("Key (%s, %b) not matched by roots:\n%s", key, blessing, roots.DebugString())
+ return fmt.Sprintf("Key (%s, %b) not matched by roots:\n%s, Recognized returns error: %v", key, blessing, roots.DebugString(), err)
}
return ""
}
@@ -129,6 +133,29 @@
}
}
+func TestRootsDump(t *testing.T) {
+ key, impl, cache := createRoots()
+
+ if err := cache.Add(key, "alice/friend"); err != nil {
+ t.Fatalf("Add failed: %v", err)
+ }
+
+ orig := impl.Dump()
+ if got := cache.Dump(); !reflect.DeepEqual(orig, got) {
+ t.Errorf("Dump() got %v, want %v", got, orig)
+ }
+
+ impl.Add(key, "carol")
+ if got := cache.Dump(); !reflect.DeepEqual(orig, got) {
+ t.Errorf("Dump() got %v, want %v", got, orig)
+ }
+
+ cache.flush()
+ if cur, got := impl.Dump(), cache.Dump(); !reflect.DeepEqual(cur, got) {
+ t.Errorf("Dump() got %v, want %v", got, cur)
+ }
+}
+
func createStore(p security.Principal) (security.BlessingStore, *cachedStore) {
var mu sync.RWMutex
impl := p.BlessingStore()
diff --git a/services/agent/internal/server/server.go b/services/agent/internal/server/server.go
index 32ae171..55e239f 100644
--- a/services/agent/internal/server/server.go
+++ b/services/agent/internal/server/server.go
@@ -435,6 +435,22 @@
return a.principal.Roots().Recognized(pkey, blessing)
}
+func (a agentd) BlessingRootsDump(_ *context.T, _ rpc.ServerCall) (map[security.BlessingPattern][][]byte, error) {
+ ret := make(map[security.BlessingPattern][][]byte)
+ a.w.rlock()
+ defer a.w.runlock()
+ for p, keys := range a.principal.Roots().Dump() {
+ for _, key := range keys {
+ marshaledKey, err := key.MarshalBinary()
+ if err != nil {
+ return nil, err
+ }
+ ret[p] = append(ret[p], marshaledKey)
+ }
+ }
+ return ret, nil
+}
+
func (a agentd) BlessingRootsDebugString(_ *context.T, _ rpc.ServerCall) (string, error) {
a.w.rlock()
defer a.w.runlock()
diff --git a/services/agent/wire.vdl b/services/agent/wire.vdl
index 4c93acb..994aaf5 100644
--- a/services/agent/wire.vdl
+++ b/services/agent/wire.vdl
@@ -55,6 +55,7 @@
BlessingRootsAdd(root []byte, pattern security.BlessingPattern) error
BlessingRootsRecognized(root []byte, blessing string) error
+ BlessingRootsDump() (map[security.BlessingPattern][][]byte | error)
BlessingRootsDebugString() (string | error)
// Clients using caching should call NotifyWhenChanged upon connecting to
diff --git a/services/agent/wire.vdl.go b/services/agent/wire.vdl.go
index 6b29ef1..1a0e6a2 100644
--- a/services/agent/wire.vdl.go
+++ b/services/agent/wire.vdl.go
@@ -65,6 +65,7 @@
BlessingStoreDebugString(*context.T, ...rpc.CallOpt) (string, error)
BlessingRootsAdd(ctx *context.T, root []byte, pattern security.BlessingPattern, opts ...rpc.CallOpt) error
BlessingRootsRecognized(ctx *context.T, root []byte, blessing string, opts ...rpc.CallOpt) error
+ BlessingRootsDump(*context.T, ...rpc.CallOpt) (map[security.BlessingPattern][][]byte, error)
BlessingRootsDebugString(*context.T, ...rpc.CallOpt) (string, error)
// Clients using caching should call NotifyWhenChanged upon connecting to
// the server. The server will stream back values whenever the client should
@@ -168,6 +169,11 @@
return
}
+func (c implAgentClientStub) BlessingRootsDump(ctx *context.T, opts ...rpc.CallOpt) (o0 map[security.BlessingPattern][][]byte, err error) {
+ err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingRootsDump", nil, []interface{}{&o0}, opts...)
+ return
+}
+
func (c implAgentClientStub) BlessingRootsDebugString(ctx *context.T, opts ...rpc.CallOpt) (o0 string, err error) {
err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingRootsDebugString", nil, []interface{}{&o0}, opts...)
return
@@ -269,6 +275,7 @@
BlessingStoreDebugString(*context.T, rpc.ServerCall) (string, error)
BlessingRootsAdd(ctx *context.T, call rpc.ServerCall, root []byte, pattern security.BlessingPattern) error
BlessingRootsRecognized(ctx *context.T, call rpc.ServerCall, root []byte, blessing string) error
+ BlessingRootsDump(*context.T, rpc.ServerCall) (map[security.BlessingPattern][][]byte, error)
BlessingRootsDebugString(*context.T, rpc.ServerCall) (string, error)
// Clients using caching should call NotifyWhenChanged upon connecting to
// the server. The server will stream back values whenever the client should
@@ -298,6 +305,7 @@
BlessingStoreDebugString(*context.T, rpc.ServerCall) (string, error)
BlessingRootsAdd(ctx *context.T, call rpc.ServerCall, root []byte, pattern security.BlessingPattern) error
BlessingRootsRecognized(ctx *context.T, call rpc.ServerCall, root []byte, blessing string) error
+ BlessingRootsDump(*context.T, rpc.ServerCall) (map[security.BlessingPattern][][]byte, error)
BlessingRootsDebugString(*context.T, rpc.ServerCall) (string, error)
// Clients using caching should call NotifyWhenChanged upon connecting to
// the server. The server will stream back values whenever the client should
@@ -399,6 +407,10 @@
return s.impl.BlessingRootsRecognized(ctx, call, i0, i1)
}
+func (s implAgentServerStub) BlessingRootsDump(ctx *context.T, call rpc.ServerCall) (map[security.BlessingPattern][][]byte, error) {
+ return s.impl.BlessingRootsDump(ctx, call)
+}
+
func (s implAgentServerStub) BlessingRootsDebugString(ctx *context.T, call rpc.ServerCall) (string, error) {
return s.impl.BlessingRootsDebugString(ctx, call)
}
@@ -554,6 +566,12 @@
},
},
{
+ Name: "BlessingRootsDump",
+ OutArgs: []rpc.ArgDesc{
+ {"", ``}, // map[security.BlessingPattern][][]byte
+ },
+ },
+ {
Name: "BlessingRootsDebugString",
OutArgs: []rpc.ArgDesc{
{"", ``}, // string