ref: Persist discharges.
The discharge cache is moved into the blessingStore so that discharges
may be used across processes.
On my machine, the first call of
'namespace glob identity' with a blessing that has a revocation caveat
takes .7s, but subsequent calls take .25 seconds.
MultiPart: 2/2
Change-Id: Ifdc23e618120baef1638b2ebda1f900b772ce727
diff --git a/lib/security/blessingstore.go b/lib/security/blessingstore.go
index 5ff62b7..0b09ee8 100644
--- a/lib/security/blessingstore.go
+++ b/lib/security/blessingstore.go
@@ -9,10 +9,13 @@
import (
"bytes"
+ "crypto/sha256"
"fmt"
"reflect"
"sort"
+ "strings"
"sync"
+ "time"
"v.io/v23/security"
"v.io/v23/verror"
@@ -27,6 +30,8 @@
errDataOrSignerUnspecified = verror.Register(pkgPath+".errDataOrSignerUnspecified", verror.NoRetry, "{1:}{2:} persisted data or signer is not specified{:_}")
)
+const cacheKeyFormat = uint32(1)
+
// blessingStore implements security.BlessingStore.
type blessingStore struct {
publicKey security.PublicKey
@@ -116,6 +121,105 @@
return m
}
+func (bs *blessingStore) CacheDischarge(discharge security.Discharge, caveat security.Caveat, impetus security.DischargeImpetus) {
+ id := discharge.ID()
+ tp := caveat.ThirdPartyDetails()
+ // Only add to the cache if the caveat did not require arguments.
+ if id == "" || tp == nil || tp.Requirements().ReportArguments {
+ return
+ }
+ key := dcacheKey(tp, impetus)
+ bs.mu.Lock()
+ defer bs.mu.Unlock()
+ old, hadold := bs.state.DischargeCache[key]
+ bs.state.DischargeCache[key] = discharge
+ if err := bs.save(); err != nil {
+ if hadold {
+ bs.state.DischargeCache[key] = old
+ } else {
+ delete(bs.state.DischargeCache, key)
+ }
+ }
+ return
+}
+
+func (bs *blessingStore) ClearDischarges(discharges ...security.Discharge) {
+ bs.mu.Lock()
+ bs.clearDischargesLocked(discharges...)
+ bs.mu.Unlock()
+ return
+}
+
+func (bs *blessingStore) clearDischargesLocked(discharges ...security.Discharge) {
+ for _, d := range discharges {
+ for k, cached := range bs.state.DischargeCache {
+ if cached.Equivalent(d) {
+ delete(bs.state.DischargeCache, k)
+ }
+ }
+ }
+}
+
+func (bs *blessingStore) Discharge(caveat security.Caveat, impetus security.DischargeImpetus) (out security.Discharge) {
+ defer bs.mu.Unlock()
+ bs.mu.Lock()
+ tp := caveat.ThirdPartyDetails()
+ if tp == nil || tp.Requirements().ReportArguments {
+ return
+ }
+ key := dcacheKey(tp, impetus)
+ if cached, exists := bs.state.DischargeCache[key]; exists {
+ out = cached
+ // If the discharge has expired, purge it from the cache.
+ if hasDischargeExpired(out) {
+ out = security.Discharge{}
+ bs.clearDischargesLocked(cached)
+ }
+ }
+ return
+}
+
+func hasDischargeExpired(dis security.Discharge) bool {
+ expiry := dis.Expiry()
+ if expiry.IsZero() {
+ return false
+ }
+ return expiry.Before(time.Now())
+}
+
+func dcacheKey(tp security.ThirdPartyCaveat, impetus security.DischargeImpetus) dischargeCacheKey {
+ // If the algorithm for computing dcacheKey changes, cacheKeyFormat must be changed as well.
+ id := tp.ID()
+ r := tp.Requirements()
+ var method, servers string
+ // We currently do not cache on impetus.Arguments because there it seems there is no
+ // general way to generate a key from them.
+ if r.ReportMethod {
+ method = impetus.Method
+ }
+ if r.ReportServer && len(impetus.Server) > 0 {
+ // Sort the server blessing patterns to increase cache usage.
+ var bps []string
+ for _, bp := range impetus.Server {
+ bps = append(bps, string(bp))
+ }
+ sort.Strings(bps)
+ servers = strings.Join(bps, ",")
+ }
+ h := sha256.New()
+ h.Write(hashString(id))
+ h.Write(hashString(method))
+ h.Write(hashString(servers))
+ var key [sha256.Size]byte
+ copy(key[:], h.Sum(nil))
+ return key
+}
+
+func hashString(d string) []byte {
+ h := sha256.Sum256([]byte(d))
+ return h[:]
+}
+
// DebugString return a human-readable string encoding of the store
// in the following format
// Default Blessings <blessings>
@@ -158,7 +262,10 @@
func newInMemoryBlessingStore(publicKey security.PublicKey) security.BlessingStore {
return &blessingStore{
publicKey: publicKey,
- state: blessingStoreState{PeerBlessings: make(map[security.BlessingPattern]security.Blessings)},
+ state: blessingStoreState{
+ PeerBlessings: make(map[security.BlessingPattern]security.Blessings),
+ DischargeCache: make(map[dischargeCacheKey]security.Discharge),
+ },
}
}
@@ -185,6 +292,10 @@
if err := decodeFromStorage(&bs.state, data, signature, bs.signer.PublicKey()); err != nil {
return err
}
+ if bs.state.CacheKeyFormat != cacheKeyFormat {
+ bs.state.CacheKeyFormat = cacheKeyFormat
+ bs.state.DischargeCache = make(map[dischargeCacheKey]security.Discharge)
+ }
return bs.verifyState()
}
@@ -197,12 +308,17 @@
}
bs := &blessingStore{
publicKey: signer.PublicKey(),
- state: blessingStoreState{PeerBlessings: make(map[security.BlessingPattern]security.Blessings)},
serializer: serializer,
signer: signer,
}
if err := bs.deserialize(); err != nil {
return nil, err
}
+ if bs.state.PeerBlessings == nil {
+ bs.state.PeerBlessings = make(map[security.BlessingPattern]security.Blessings)
+ }
+ if bs.state.DischargeCache == nil {
+ bs.state.DischargeCache = make(map[dischargeCacheKey]security.Discharge)
+ }
return bs, nil
}
diff --git a/lib/security/blessingstore_test.go b/lib/security/blessingstore_test.go
index 339cd59..01cc3df 100644
--- a/lib/security/blessingstore_test.go
+++ b/lib/security/blessingstore_test.go
@@ -140,6 +140,7 @@
if err := tester.testSetDefault(s); err != nil {
t.Error(err)
}
+ testDischargeCache(t, s)
}
func TestBlessingStorePersistence(t *testing.T) {
@@ -164,6 +165,7 @@
if err := tester.testSetDefault(s); err != nil {
t.Error(err)
}
+ testDischargeCache(t, s)
// Recreate the BlessingStore from the directory.
p2, err := LoadPersistentPrincipal(dir, nil)
diff --git a/lib/security/discharge_cache_test.go b/lib/security/discharge_cache_test.go
new file mode 100644
index 0000000..f974d1a
--- /dev/null
+++ b/lib/security/discharge_cache_test.go
@@ -0,0 +1,105 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package security
+
+import (
+ "testing"
+ "time"
+
+ "v.io/v23/security"
+ "v.io/v23/vdl"
+)
+
+func testDischargeCache(t *testing.T, s security.BlessingStore) {
+ var (
+ discharger = mkPrincipal()
+ expiredCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{}, security.UnconstrainedUse()))
+ argsCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "peoria", security.ThirdPartyRequirements{ReportArguments: true}, security.UnconstrainedUse()))
+ methodCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{ReportMethod: true}, security.UnconstrainedUse()))
+ serverCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "peoria", security.ThirdPartyRequirements{ReportServer: true}, security.UnconstrainedUse()))
+
+ dEmpty = security.Discharge{}
+ dExpired = mkDischarge(discharger.MintDischarge(expiredCav, mkCaveat(security.NewExpiryCaveat(time.Now().Add(-1*time.Minute)))))
+ dArgs = mkDischarge(discharger.MintDischarge(argsCav, security.UnconstrainedUse()))
+ dMethod = mkDischarge(discharger.MintDischarge(methodCav, security.UnconstrainedUse()))
+ dServer = mkDischarge(discharger.MintDischarge(serverCav, security.UnconstrainedUse()))
+
+ emptyImp = security.DischargeImpetus{}
+ argsImp = security.DischargeImpetus{Arguments: []*vdl.Value{&vdl.Value{}}}
+ methodImp = security.DischargeImpetus{Method: "foo"}
+ otherMethodImp = security.DischargeImpetus{Method: "bar"}
+ serverImp = security.DischargeImpetus{Server: []security.BlessingPattern{security.BlessingPattern("fooserver")}}
+ otherServerImp = security.DischargeImpetus{Server: []security.BlessingPattern{security.BlessingPattern("barserver")}}
+ )
+
+ // Discharges for different cavs should not be cached.
+ d := mkDischarge(discharger.MintDischarge(argsCav, security.UnconstrainedUse()))
+ s.CacheDischarge(d, argsCav, emptyImp)
+ if d := s.Discharge(methodCav, emptyImp); d.ID() != "" {
+ t.Errorf("Discharge for different caveat should not have been in cache")
+ }
+ s.ClearDischarges(d)
+
+ // Add some discharges into the cache.
+ s.CacheDischarge(dArgs, argsCav, argsImp)
+ s.CacheDischarge(dMethod, methodCav, methodImp)
+ s.CacheDischarge(dServer, serverCav, serverImp)
+ s.CacheDischarge(dExpired, expiredCav, emptyImp)
+
+ testCases := []struct {
+ caveat security.Caveat // caveat that we are fetching discharges for.
+ queryImpetus security.DischargeImpetus // Impetus used to query the cache.
+ cachedDischarge security.Discharge // Discharge that we expect to be returned from the cache, nil if the discharge should not be cached.
+ }{
+ // Expired discharges should not be returned by the cache.
+ {expiredCav, emptyImp, dEmpty},
+
+ // Discharges with Impetuses that have Arguments should not be cached.
+ {argsCav, argsImp, dEmpty},
+
+ {methodCav, methodImp, dMethod},
+ {methodCav, otherMethodImp, dEmpty},
+ {methodCav, emptyImp, dEmpty},
+
+ {serverCav, serverImp, dServer},
+ {serverCav, otherServerImp, dEmpty},
+ {serverCav, emptyImp, dEmpty},
+ }
+
+ for i, test := range testCases {
+ out := s.Discharge(test.caveat, test.queryImpetus)
+ if got := out.ID(); got != test.cachedDischarge.ID() {
+ t.Errorf("#%d: got discharge %v, want %v, queried with %v", i, got, test.cachedDischarge.ID(), test.queryImpetus)
+ }
+ }
+ if t.Failed() {
+ t.Logf("dArgs.ID(): %v", dArgs.ID())
+ t.Logf("dMethod.ID(): %v", dMethod.ID())
+ t.Logf("dServer.ID(): %v", dServer.ID())
+ t.Logf("dExpired.ID(): %v", dExpired.ID())
+ }
+}
+
+func mkPrincipal() security.Principal {
+ p, err := NewPrincipal()
+ if err != nil {
+ panic(err)
+ }
+ return p
+}
+
+func mkDischarge(d security.Discharge, err error) security.Discharge {
+ if err != nil {
+ panic(err)
+ }
+ return d
+}
+
+func mkCaveat(c security.Caveat, err error) security.Caveat {
+ if err != nil {
+ panic(err)
+ }
+ return c
+}
diff --git a/lib/security/type.vdl b/lib/security/type.vdl
index 9d65571..9539700 100644
--- a/lib/security/type.vdl
+++ b/lib/security/type.vdl
@@ -18,4 +18,11 @@
// DefaultBlessings is the default Blessings to be shared with peers for which
// no other information is available to select blessings.
DefaultBlessings security.WireBlessings
+ // DischargeCache is the cache of discharges.
+ DischargeCache map[dischargeCacheKey]security.WireDischarge
+ // CacheKeyFormat is the dischargeCacheKey format version. It should incremented
+ // any time the format of the dischargeCacheKey is changed.
+ CacheKeyFormat uint32
}
+
+type dischargeCacheKey [32]byte
diff --git a/lib/security/type.vdl.go b/lib/security/type.vdl.go
index 3fabed7..5681451 100644
--- a/lib/security/type.vdl.go
+++ b/lib/security/type.vdl.go
@@ -32,6 +32,11 @@
// DefaultBlessings is the default Blessings to be shared with peers for which
// no other information is available to select blessings.
DefaultBlessings security.Blessings
+ // DischargeCache is the cache of discharges.
+ DischargeCache map[dischargeCacheKey]security.Discharge
+ // CacheKeyFormat is the dischargeCacheKey format version. It should incremented
+ // any time the format of the dischargeCacheKey is changed.
+ CacheKeyFormat uint32
}
func (blessingStoreState) __VDLReflect(struct {
@@ -39,7 +44,15 @@
}) {
}
+type dischargeCacheKey [32]byte
+
+func (dischargeCacheKey) __VDLReflect(struct {
+ Name string `vdl:"v.io/x/ref/lib/security.dischargeCacheKey"`
+}) {
+}
+
func init() {
vdl.Register((*blessingRootsState)(nil))
vdl.Register((*blessingStoreState)(nil))
+ vdl.Register((*dischargeCacheKey)(nil))
}
diff --git a/runtime/internal/rpc/client.go b/runtime/internal/rpc/client.go
index d7aa5b8..9c81d74 100644
--- a/runtime/internal/rpc/client.go
+++ b/runtime/internal/rpc/client.go
@@ -1018,7 +1018,7 @@
// to detect it, we conservatively flush all discharges we used from the cache.
// TODO(ataly,andreser): add verror.BadDischarge and handle it explicitly?
vlog.VI(3).Infof("Discarding %d discharges as RPC failed with %v", len(fc.discharges), fc.response.Error)
- fc.dc.Invalidate(fc.discharges...)
+ fc.dc.Invalidate(fc.ctx, fc.discharges...)
}
if id == errBadNumInputArgs.ID || id == errBadInputArg.ID {
return fc.close(verror.New(verror.ErrBadProtocol, fc.ctx, fc.response.Error))
diff --git a/runtime/internal/rpc/discharges.go b/runtime/internal/rpc/discharges.go
index 46017ee..d69e349 100644
--- a/runtime/internal/rpc/discharges.go
+++ b/runtime/internal/rpc/discharges.go
@@ -5,14 +5,13 @@
package rpc
import (
- "sort"
- "strings"
"sync"
"time"
"v.io/x/ref/lib/apilog"
"v.io/x/ref/runtime/internal/rpc/stream/vc"
+ "v.io/v23"
"v.io/v23/context"
"v.io/v23/rpc"
"v.io/v23/security"
@@ -35,7 +34,6 @@
type dischargeClient struct {
c rpc.Client
defaultCtx *context.T
- cache dischargeCache
dischargeExpiryBuffer time.Duration
}
@@ -51,12 +49,8 @@
// Attempts will be made to refresh a discharge DischargeExpiryBuffer before they expire.
func InternalNewDischargeClient(defaultCtx *context.T, client rpc.Client, dischargeExpiryBuffer time.Duration) vc.DischargeClient {
return &dischargeClient{
- c: client,
- defaultCtx: defaultCtx,
- cache: dischargeCache{
- cache: make(map[dischargeCacheKey]security.Discharge),
- idToKeys: make(map[string][]dischargeCacheKey),
- },
+ c: client,
+ defaultCtx: defaultCtx,
dischargeExpiryBuffer: dischargeExpiryBuffer,
}
}
@@ -85,15 +79,15 @@
}
}
+ if ctx == nil {
+ ctx = d.defaultCtx
+ }
+ bstore := v23.GetPrincipal(ctx).BlessingStore()
// Gather discharges from cache.
- // (Collect a set of pointers, where nil implies a missing discharge)
- discharges := make([]*security.Discharge, len(caveats))
- if d.cache.Discharges(caveats, filteredImpetuses, discharges) > 0 {
+ discharges, rem := discharges(bstore, caveats, impetus)
+ if rem > 0 {
// Fetch discharges for caveats for which no discharges were
// found in the cache.
- if ctx == nil {
- ctx = d.defaultCtx
- }
if ctx != nil {
var span vtrace.Span
ctx, span = vtrace.WithNewSpan(ctx, "Fetching Discharges")
@@ -102,14 +96,30 @@
d.fetchDischarges(ctx, caveats, filteredImpetuses, discharges)
}
for _, d := range discharges {
- if d != nil {
- ret = append(ret, *d)
+ if d.ID() != "" {
+ ret = append(ret, d)
}
}
return
}
-func (d *dischargeClient) Invalidate(discharges ...security.Discharge) {
- d.cache.invalidate(discharges...)
+
+func discharges(bs security.BlessingStore, caveats []security.Caveat, imp security.DischargeImpetus) (out []security.Discharge, rem int) {
+ out = make([]security.Discharge, len(caveats))
+ for i := range caveats {
+ out[i] = bs.Discharge(caveats[i], imp)
+ if out[i].ID() == "" {
+ rem++
+ }
+ }
+ return
+}
+
+func (d *dischargeClient) Invalidate(ctx *context.T, discharges ...security.Discharge) {
+ if ctx == nil {
+ ctx = d.defaultCtx
+ }
+ bstore := v23.GetPrincipal(ctx).BlessingStore()
+ bstore.ClearDischarges(discharges...)
}
// fetchDischarges fills out by fetching discharges for caveats from the
@@ -118,12 +128,14 @@
// fetched or no new discharges are fetched.
// REQUIRES: len(caveats) == len(out)
// REQUIRES: caveats[i].ThirdPartyDetails() != nil for 0 <= i < len(caveats)
-func (d *dischargeClient) fetchDischarges(ctx *context.T, caveats []security.Caveat, impetuses []security.DischargeImpetus, out []*security.Discharge) {
+func (d *dischargeClient) fetchDischarges(ctx *context.T, caveats []security.Caveat, impetuses []security.DischargeImpetus, out []security.Discharge) {
+ bstore := v23.GetPrincipal(ctx).BlessingStore()
var wg sync.WaitGroup
for {
type fetched struct {
idx int
- discharge *security.Discharge
+ discharge security.Discharge
+ caveat security.Caveat
impetus security.DischargeImpetus
}
discharges := make(chan fetched, len(caveats))
@@ -143,14 +155,14 @@
vlog.VI(3).Infof("Discharge fetch for %v failed: %v", tp, err)
return
}
- discharges <- fetched{i, &dis, impetuses[i]}
+ discharges <- fetched{i, dis, caveats[i], impetuses[i]}
}(i, ctx, caveats[i])
}
wg.Wait()
close(discharges)
var got int
for fetched := range discharges {
- d.cache.Add(*fetched.discharge, fetched.impetus)
+ bstore.CacheDischarge(fetched.discharge, fetched.caveat, fetched.impetus)
out[fetched.idx] = fetched.discharge
got++
}
@@ -163,117 +175,6 @@
}
}
-func (d *dischargeClient) shouldFetchDischarge(dis *security.Discharge) bool {
- if dis == nil {
- return true
- }
- expiry := dis.Expiry()
- if expiry.IsZero() {
- return false
- }
- return expiry.Before(time.Now().Add(d.dischargeExpiryBuffer))
-}
-
-// dischargeCache is a concurrency-safe cache for third party caveat discharges.
-type dischargeCache struct {
- mu sync.RWMutex
- cache map[dischargeCacheKey]security.Discharge // GUARDED_BY(mu)
- idToKeys map[string][]dischargeCacheKey // GUARDED_BY(mu)
-}
-
-type dischargeCacheKey struct {
- id, method, serverPatterns string
-}
-
-func (dcc *dischargeCache) cacheKey(id string, impetus security.DischargeImpetus) dischargeCacheKey {
- // We currently do not cache on impetus.Arguments because there it seems there is no
- // universal way to generate a key from them.
- // Add sorted BlessingPatterns to the key.
- var bps []string
- for _, bp := range impetus.Server {
- bps = append(bps, string(bp))
- }
- sort.Strings(bps)
- return dischargeCacheKey{
- id: id,
- method: impetus.Method,
- serverPatterns: strings.Join(bps, ","), // "," is restricted in blessingPatterns.
- }
-}
-
-// Add inserts the argument to the cache, the previous discharge for the same caveat.
-func (dcc *dischargeCache) Add(d security.Discharge, filteredImpetus security.DischargeImpetus) {
- // Only add to the cache if the caveat did not require arguments.
- if len(filteredImpetus.Arguments) > 0 {
- return
- }
- id := d.ID()
- dcc.mu.Lock()
- dcc.cache[dcc.cacheKey(id, filteredImpetus)] = d
- if _, ok := dcc.idToKeys[id]; !ok {
- dcc.idToKeys[id] = []dischargeCacheKey{}
- }
- dcc.idToKeys[id] = append(dcc.idToKeys[id], dcc.cacheKey(id, filteredImpetus))
- dcc.mu.Unlock()
-}
-
-// Discharges takes a slice of caveats, a slice of filtered Discharge impetuses
-// corresponding to the caveats, and a slice of discharges of the same length and
-// fills in nil entries in the discharges slice with pointers to cached discharges
-// (if there are any).
-//
-// REQUIRES: len(caveats) == len(impetuses) == len(out)
-// REQUIRES: caveats[i].ThirdPartyDetails() != nil, for all 0 <= i < len(caveats)
-func (dcc *dischargeCache) Discharges(caveats []security.Caveat, impetuses []security.DischargeImpetus, out []*security.Discharge) (remaining int) {
- dcc.mu.Lock()
- for i, d := range out {
- if d != nil {
- continue
- }
- id := caveats[i].ThirdPartyDetails().ID()
- key := dcc.cacheKey(id, impetuses[i])
- if cached, exists := dcc.cache[key]; exists {
- out[i] = &cached
- // If the discharge has expired, purge it from the cache.
- if hasDischargeExpired(out[i]) {
- out[i] = nil
- delete(dcc.cache, key)
- remaining++
- }
- } else {
- remaining++
- }
- }
- dcc.mu.Unlock()
- return
-}
-
-func hasDischargeExpired(dis *security.Discharge) bool {
- expiry := dis.Expiry()
- if expiry.IsZero() {
- return false
- }
- return expiry.Before(time.Now())
-}
-
-func (dcc *dischargeCache) invalidate(discharges ...security.Discharge) {
- dcc.mu.Lock()
- for _, d := range discharges {
- if keys, ok := dcc.idToKeys[d.ID()]; ok {
- var newKeys []dischargeCacheKey
- for _, k := range keys {
- if cached := dcc.cache[k]; cached.Equivalent(d) {
- delete(dcc.cache, k)
- } else {
- newKeys = append(newKeys, k)
- }
- }
- dcc.idToKeys[d.ID()] = newKeys
- }
- }
- dcc.mu.Unlock()
-}
-
// filteredImpetus returns a copy of 'before' after removing any values that are not required as per 'r'.
func filteredImpetus(r security.ThirdPartyRequirements, before security.DischargeImpetus) (after security.DischargeImpetus) {
if r.ReportServer && len(before.Server) > 0 {
@@ -293,3 +194,14 @@
}
return
}
+
+func (d *dischargeClient) shouldFetchDischarge(dis security.Discharge) bool {
+ if dis.ID() == "" {
+ return true
+ }
+ expiry := dis.Expiry()
+ if expiry.IsZero() {
+ return false
+ }
+ return expiry.Before(time.Now().Add(d.dischargeExpiryBuffer))
+}
diff --git a/runtime/internal/rpc/discharges_test.go b/runtime/internal/rpc/discharges_test.go
deleted file mode 100644
index 7b67b1b..0000000
--- a/runtime/internal/rpc/discharges_test.go
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package rpc
-
-import (
- "testing"
- "time"
-
- "v.io/v23/security"
- "v.io/v23/vdl"
- "v.io/x/ref/test/testutil"
-)
-
-func TestDischargeClientCache(t *testing.T) {
- dcc := &dischargeCache{
- cache: make(map[dischargeCacheKey]security.Discharge),
- idToKeys: make(map[string][]dischargeCacheKey),
- }
-
- var (
- discharger = testutil.NewPrincipal("discharger")
- expiredCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{}, security.UnconstrainedUse()))
- argsCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{}, security.UnconstrainedUse()))
- methodCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{}, security.UnconstrainedUse()))
- serverCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{}, security.UnconstrainedUse()))
-
- dExpired = mkDischarge(discharger.MintDischarge(expiredCav, mkCaveat(security.NewExpiryCaveat(time.Now().Add(-1*time.Minute)))))
- dArgs = mkDischarge(discharger.MintDischarge(argsCav, security.UnconstrainedUse()))
- dMethod = mkDischarge(discharger.MintDischarge(methodCav, security.UnconstrainedUse()))
- dServer = mkDischarge(discharger.MintDischarge(serverCav, security.UnconstrainedUse()))
-
- emptyImp = security.DischargeImpetus{}
- argsImp = security.DischargeImpetus{Arguments: []*vdl.Value{&vdl.Value{}}}
- methodImp = security.DischargeImpetus{Method: "foo"}
- otherMethodImp = security.DischargeImpetus{Method: "bar"}
- serverImp = security.DischargeImpetus{Server: []security.BlessingPattern{security.BlessingPattern("fooserver")}}
- otherServerImp = security.DischargeImpetus{Server: []security.BlessingPattern{security.BlessingPattern("barserver")}}
- )
-
- // Discharges for different cavs should not be cached.
- d := mkDischarge(discharger.MintDischarge(argsCav, security.UnconstrainedUse()))
- dcc.Add(d, emptyImp)
- outdis := make([]*security.Discharge, 1)
- if remaining := dcc.Discharges([]security.Caveat{methodCav}, []security.DischargeImpetus{emptyImp}, outdis); remaining == 0 {
- t.Errorf("Discharge for different caveat should not have been in cache")
- }
- dcc.invalidate(d)
-
- // Add some discharges into the cache.
- dcc.Add(dArgs, argsImp)
- dcc.Add(dMethod, methodImp)
- dcc.Add(dServer, serverImp)
- dcc.Add(dExpired, emptyImp)
-
- testCases := []struct {
- caveat security.Caveat // caveat that we are fetching discharges for.
- queryImpetus security.DischargeImpetus // Impetus used to query the cache.
- cachedDischarge *security.Discharge // Discharge that we expect to be returned from the cache, nil if the discharge should not be cached.
- }{
- // Expired discharges should not be returned by the cache.
- {expiredCav, emptyImp, nil},
-
- // Discharges with Impetuses that have Arguments should not be cached.
- {argsCav, argsImp, nil},
-
- {methodCav, methodImp, &dMethod},
- {methodCav, otherMethodImp, nil},
- {methodCav, emptyImp, nil},
-
- {serverCav, serverImp, &dServer},
- {serverCav, otherServerImp, nil},
- {serverCav, emptyImp, nil},
- }
-
- for i, test := range testCases {
- out := make([]*security.Discharge, 1)
- remaining := dcc.Discharges([]security.Caveat{test.caveat}, []security.DischargeImpetus{test.queryImpetus}, out)
- if test.cachedDischarge != nil {
- got := "nil"
- if remaining == 0 {
- got = out[0].ID()
- }
- if got != test.cachedDischarge.ID() {
- t.Errorf("#%d: got discharge %v, want %v, queried with %v", i, got, test.cachedDischarge.ID(), test.queryImpetus)
- }
- } else if remaining == 0 {
- t.Errorf("#%d: discharge %v should not have been in cache, queried with %v", i, out[0].ID(), test.queryImpetus)
- }
- }
- if t.Failed() {
- t.Logf("dArgs.ID(): %v", dArgs.ID())
- t.Logf("dMethod.ID(): %v", dMethod.ID())
- t.Logf("dServer.ID(): %v", dServer.ID())
- t.Logf("dExpired.ID(): %v", dExpired.ID())
- }
-}
-
-func mkDischarge(d security.Discharge, err error) security.Discharge {
- if err != nil {
- panic(err)
- }
- return d
-}
diff --git a/runtime/internal/rpc/full_test.go b/runtime/internal/rpc/full_test.go
index f60009b..8224776 100644
--- a/runtime/internal/rpc/full_test.go
+++ b/runtime/internal/rpc/full_test.go
@@ -1193,6 +1193,15 @@
func (*singleBlessingStore) PeerBlessings() map[security.BlessingPattern]security.Blessings {
return nil
}
+func (*singleBlessingStore) CacheDischarge(security.Discharge, security.Caveat, security.DischargeImpetus) {
+ return
+}
+func (*singleBlessingStore) ClearDischarges(...security.Discharge) {
+ return
+}
+func (*singleBlessingStore) Discharge(security.Caveat, security.DischargeImpetus) security.Discharge {
+ return security.Discharge{}
+}
// singleBlessingPrincipal implements security.Principal. It is a wrapper over
// a security.Principal that intercepts all invocations on the
diff --git a/runtime/internal/rpc/stream/vc/vc.go b/runtime/internal/rpc/stream/vc/vc.go
index 12c2527..805a07f 100644
--- a/runtime/internal/rpc/stream/vc/vc.go
+++ b/runtime/internal/rpc/stream/vc/vc.go
@@ -199,7 +199,7 @@
PrepareDischarges(ctx *context.T, forcaveats []security.Caveat, impetus security.DischargeImpetus) []security.Discharge
// Invalidate marks the provided discharges as invalid, and therefore unfit
// for being returned by a subsequent PrepareDischarges call.
- Invalidate(discharges ...security.Discharge)
+ Invalidate(ctx *context.T, discharges ...security.Discharge)
RPCStreamListenerOpt()
}
diff --git a/runtime/internal/rpc/stream/vc/vc_test.go b/runtime/internal/rpc/stream/vc/vc_test.go
index e42c385..6e2737f 100644
--- a/runtime/internal/rpc/stream/vc/vc_test.go
+++ b/runtime/internal/rpc/stream/vc/vc_test.go
@@ -170,9 +170,9 @@
func (m mockDischargeClient) PrepareDischarges(_ *context.T, forcaveats []security.Caveat, impetus security.DischargeImpetus) []security.Discharge {
return m
}
-func (mockDischargeClient) Invalidate(...security.Discharge) {}
-func (mockDischargeClient) RPCStreamListenerOpt() {}
-func (mockDischargeClient) RPCStreamVCOpt() {}
+func (mockDischargeClient) Invalidate(*context.T, ...security.Discharge) {}
+func (mockDischargeClient) RPCStreamListenerOpt() {}
+func (mockDischargeClient) RPCStreamVCOpt() {}
// Test that mockDischargeClient implements vc.DischargeClient.
var _ vc.DischargeClient = (mockDischargeClient)(nil)
diff --git a/services/agent/agentlib/client.go b/services/agent/agentlib/client.go
index 7dc2c3c..2efad70 100644
--- a/services/agent/agentlib/client.go
+++ b/services/agent/agentlib/client.go
@@ -252,6 +252,28 @@
return
}
+func (b *blessingStore) CacheDischarge(d security.Discharge, c security.Caveat, i security.DischargeImpetus) {
+ err := b.caller.call("BlessingStoreCacheDischarge", results(), d, c, i)
+ if err != nil {
+ vlog.Errorf("error calling BlessingStoreCacheDischarge: %v", err)
+ }
+}
+
+func (b *blessingStore) ClearDischarges(discharges ...security.Discharge) {
+ err := b.caller.call("BlessingStoreClearDischarges", results(), discharges)
+ if err != nil {
+ vlog.Errorf("error calling BlessingStoreClearDischarges: %v", err)
+ }
+}
+
+func (b *blessingStore) Discharge(caveat security.Caveat, impetus security.DischargeImpetus) (out security.Discharge) {
+ err := b.caller.call("BlessingStoreDischarge", results(&out), caveat, impetus)
+ if err != nil {
+ vlog.Errorf("error calling BlessingStoreDischarge: %v", err)
+ }
+ return
+}
+
type blessingRoots struct {
caller caller
}
diff --git a/services/agent/internal/cache/cache.go b/services/agent/internal/cache/cache.go
index cc6b904..70bca36 100644
--- a/services/agent/internal/cache/cache.go
+++ b/services/agent/internal/cache/cache.go
@@ -312,6 +312,24 @@
return fmt.Sprintf("cached[%s]", s.impl)
}
+func (s *cachedStore) CacheDischarge(d security.Discharge, c security.Caveat, i security.DischargeImpetus) {
+ s.mu.Lock()
+ s.impl.CacheDischarge(d, c, i)
+ s.mu.Unlock()
+}
+
+func (s *cachedStore) ClearDischarges(discharges ...security.Discharge) {
+ s.mu.Lock()
+ s.impl.ClearDischarges(discharges...)
+ s.mu.Unlock()
+}
+
+func (s *cachedStore) Discharge(caveat security.Caveat, impetus security.DischargeImpetus) security.Discharge {
+ defer s.mu.Unlock()
+ s.mu.Lock()
+ return s.impl.Discharge(caveat, impetus)
+}
+
// Must be called while holding mu.
func (s *cachedStore) flush() {
s.hasDef = false
diff --git a/services/agent/internal/server/server.go b/services/agent/internal/server/server.go
index 893d4e7..eab49a2 100644
--- a/services/agent/internal/server/server.go
+++ b/services/agent/internal/server/server.go
@@ -403,6 +403,26 @@
return a.principal.BlessingStore().Default(), nil
}
+func (a agentd) BlessingStoreCacheDischarge(_ *context.T, _ rpc.ServerCall, discharge security.Discharge, caveat security.Caveat, impetus security.DischargeImpetus) error {
+ a.w.lock()
+ a.principal.BlessingStore().CacheDischarge(discharge, caveat, impetus)
+ a.w.unlock(a.id)
+ return nil
+}
+
+func (a agentd) BlessingStoreClearDischarges(_ *context.T, _ rpc.ServerCall, discharges []security.Discharge) error {
+ a.w.lock()
+ a.principal.BlessingStore().ClearDischarges(discharges...)
+ a.w.unlock(a.id)
+ return nil
+}
+
+func (a agentd) BlessingStoreDischarge(_ *context.T, _ rpc.ServerCall, caveat security.Caveat, impetus security.DischargeImpetus) (security.Discharge, error) {
+ a.w.lock()
+ defer a.w.unlock(a.id)
+ return a.principal.BlessingStore().Discharge(caveat, impetus), nil
+}
+
func (a agentd) BlessingRootsAdd(_ *context.T, _ rpc.ServerCall, root []byte, pattern security.BlessingPattern) error {
pkey, err := security.UnmarshalPublicKey(root)
if err != nil {
diff --git a/services/agent/internal/test_principal/main.go b/services/agent/internal/test_principal/main.go
index 28cc564..7887d67 100644
--- a/services/agent/internal/test_principal/main.go
+++ b/services/agent/internal/test_principal/main.go
@@ -94,7 +94,8 @@
if err != nil {
errorf("security.NewPublicKeyCaveat: %v", err)
}
- if _, err := p.MintDischarge(tpcav, cav); err != nil {
+ dis, err := p.MintDischarge(tpcav, cav)
+ if err != nil {
errorf("MintDischarge: %v", err)
}
// BlessingRoots
@@ -134,6 +135,14 @@
if forpeer := p.BlessingStore().ForPeer("superman/friend"); !reflect.DeepEqual(forpeer, b) {
errorf("BlessingStore().ForPeer returned %v and not %v", forpeer, b)
}
+ p.BlessingStore().CacheDischarge(dis, tpcav, security.DischargeImpetus{})
+ if got := p.BlessingStore().Discharge(tpcav, security.DischargeImpetus{}); !dis.Equivalent(got) {
+ errorf("BlessingStore().Discharges returned %#v want %#v", got, dis)
+ }
+ p.BlessingStore().ClearDischarges(dis)
+ if got := p.BlessingStore().Discharge(tpcav, security.DischargeImpetus{}); got.ID() != "" {
+ errorf("BlessingStore().Discharges returned %#v want empty", got)
+ }
if len(errors) > 0 {
// Print out all errors and exit with failure.
diff --git a/services/agent/wire.vdl b/services/agent/wire.vdl
index 994aaf5..1d92fcc 100644
--- a/services/agent/wire.vdl
+++ b/services/agent/wire.vdl
@@ -52,6 +52,9 @@
BlessingStoreDefault() (security.WireBlessings | error)
BlessingStorePeerBlessings() (map[security.BlessingPattern]security.WireBlessings | error)
BlessingStoreDebugString() (string | error)
+ BlessingStoreCacheDischarge(discharge security.WireDischarge, caveat security.Caveat, impetus security.DischargeImpetus) error
+ BlessingStoreClearDischarges(discharges []security.WireDischarge) error
+ BlessingStoreDischarge(caveat security.Caveat, impetus security.DischargeImpetus) (wd security.WireDischarge | error)
BlessingRootsAdd(root []byte, pattern security.BlessingPattern) error
BlessingRootsRecognized(root []byte, blessing string) error
diff --git a/services/agent/wire.vdl.go b/services/agent/wire.vdl.go
index 1a0e6a2..da18067 100644
--- a/services/agent/wire.vdl.go
+++ b/services/agent/wire.vdl.go
@@ -63,6 +63,9 @@
BlessingStoreDefault(*context.T, ...rpc.CallOpt) (security.Blessings, error)
BlessingStorePeerBlessings(*context.T, ...rpc.CallOpt) (map[security.BlessingPattern]security.Blessings, error)
BlessingStoreDebugString(*context.T, ...rpc.CallOpt) (string, error)
+ BlessingStoreCacheDischarge(ctx *context.T, discharge security.Discharge, caveat security.Caveat, impetus security.DischargeImpetus, opts ...rpc.CallOpt) error
+ BlessingStoreClearDischarges(ctx *context.T, discharges []security.Discharge, opts ...rpc.CallOpt) error
+ BlessingStoreDischarge(ctx *context.T, caveat security.Caveat, impetus security.DischargeImpetus, opts ...rpc.CallOpt) (wd security.Discharge, err 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)
@@ -159,6 +162,21 @@
return
}
+func (c implAgentClientStub) BlessingStoreCacheDischarge(ctx *context.T, i0 security.Discharge, i1 security.Caveat, i2 security.DischargeImpetus, opts ...rpc.CallOpt) (err error) {
+ err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreCacheDischarge", []interface{}{i0, i1, i2}, nil, opts...)
+ return
+}
+
+func (c implAgentClientStub) BlessingStoreClearDischarges(ctx *context.T, i0 []security.Discharge, opts ...rpc.CallOpt) (err error) {
+ err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreClearDischarges", []interface{}{i0}, nil, opts...)
+ return
+}
+
+func (c implAgentClientStub) BlessingStoreDischarge(ctx *context.T, i0 security.Caveat, i1 security.DischargeImpetus, opts ...rpc.CallOpt) (o0 security.Discharge, err error) {
+ err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingStoreDischarge", []interface{}{i0, i1}, []interface{}{&o0}, opts...)
+ return
+}
+
func (c implAgentClientStub) BlessingRootsAdd(ctx *context.T, i0 []byte, i1 security.BlessingPattern, opts ...rpc.CallOpt) (err error) {
err = v23.GetClient(ctx).Call(ctx, c.name, "BlessingRootsAdd", []interface{}{i0, i1}, nil, opts...)
return
@@ -273,6 +291,9 @@
BlessingStoreDefault(*context.T, rpc.ServerCall) (security.Blessings, error)
BlessingStorePeerBlessings(*context.T, rpc.ServerCall) (map[security.BlessingPattern]security.Blessings, error)
BlessingStoreDebugString(*context.T, rpc.ServerCall) (string, error)
+ BlessingStoreCacheDischarge(ctx *context.T, call rpc.ServerCall, discharge security.Discharge, caveat security.Caveat, impetus security.DischargeImpetus) error
+ BlessingStoreClearDischarges(ctx *context.T, call rpc.ServerCall, discharges []security.Discharge) error
+ BlessingStoreDischarge(ctx *context.T, call rpc.ServerCall, caveat security.Caveat, impetus security.DischargeImpetus) (wd security.Discharge, err 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)
@@ -303,6 +324,9 @@
BlessingStoreDefault(*context.T, rpc.ServerCall) (security.Blessings, error)
BlessingStorePeerBlessings(*context.T, rpc.ServerCall) (map[security.BlessingPattern]security.Blessings, error)
BlessingStoreDebugString(*context.T, rpc.ServerCall) (string, error)
+ BlessingStoreCacheDischarge(ctx *context.T, call rpc.ServerCall, discharge security.Discharge, caveat security.Caveat, impetus security.DischargeImpetus) error
+ BlessingStoreClearDischarges(ctx *context.T, call rpc.ServerCall, discharges []security.Discharge) error
+ BlessingStoreDischarge(ctx *context.T, call rpc.ServerCall, caveat security.Caveat, impetus security.DischargeImpetus) (wd security.Discharge, err 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)
@@ -399,6 +423,18 @@
return s.impl.BlessingStoreDebugString(ctx, call)
}
+func (s implAgentServerStub) BlessingStoreCacheDischarge(ctx *context.T, call rpc.ServerCall, i0 security.Discharge, i1 security.Caveat, i2 security.DischargeImpetus) error {
+ return s.impl.BlessingStoreCacheDischarge(ctx, call, i0, i1, i2)
+}
+
+func (s implAgentServerStub) BlessingStoreClearDischarges(ctx *context.T, call rpc.ServerCall, i0 []security.Discharge) error {
+ return s.impl.BlessingStoreClearDischarges(ctx, call, i0)
+}
+
+func (s implAgentServerStub) BlessingStoreDischarge(ctx *context.T, call rpc.ServerCall, i0 security.Caveat, i1 security.DischargeImpetus) (security.Discharge, error) {
+ return s.impl.BlessingStoreDischarge(ctx, call, i0, i1)
+}
+
func (s implAgentServerStub) BlessingRootsAdd(ctx *context.T, call rpc.ServerCall, i0 []byte, i1 security.BlessingPattern) error {
return s.impl.BlessingRootsAdd(ctx, call, i0, i1)
}
@@ -552,6 +588,30 @@
},
},
{
+ Name: "BlessingStoreCacheDischarge",
+ InArgs: []rpc.ArgDesc{
+ {"discharge", ``}, // security.Discharge
+ {"caveat", ``}, // security.Caveat
+ {"impetus", ``}, // security.DischargeImpetus
+ },
+ },
+ {
+ Name: "BlessingStoreClearDischarges",
+ InArgs: []rpc.ArgDesc{
+ {"discharges", ``}, // []security.Discharge
+ },
+ },
+ {
+ Name: "BlessingStoreDischarge",
+ InArgs: []rpc.ArgDesc{
+ {"caveat", ``}, // security.Caveat
+ {"impetus", ``}, // security.DischargeImpetus
+ },
+ OutArgs: []rpc.ArgDesc{
+ {"wd", ``}, // security.Discharge
+ },
+ },
+ {
Name: "BlessingRootsAdd",
InArgs: []rpc.ArgDesc{
{"root", ``}, // []byte