veyron/runtimes/google/ipc: use discharges for third-party caveat authorization
Change-Id: I385e60c5f7115a2a3472481a098335b8c2c8cd85
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index 49b3915..0aa2458 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -40,6 +40,8 @@
vcMapMu sync.Mutex
// TODO(ashankar): Additionally, should vcMap be keyed with other options also?
vcMap map[string]*vcInfo // map from endpoint.String() to vc info
+
+ dischargeCache dischargeCache
}
type vcInfo struct {
@@ -50,10 +52,11 @@
func InternalNewClient(streamMgr stream.Manager, ns naming.Namespace, opts ...ipc.ClientOpt) (ipc.Client, error) {
c := &client{
- streamMgr: streamMgr,
- ns: ns,
- vcMap: make(map[string]*vcInfo),
- callTimeout: defaultCallTimeout,
+ streamMgr: streamMgr,
+ ns: ns,
+ vcMap: make(map[string]*vcInfo),
+ callTimeout: defaultCallTimeout,
+ dischargeCache: dischargeCache{CaveatDischargeMap: make(security.CaveatDischargeMap)},
}
for _, opt := range opts {
// Collect all client opts that are also vc opts.
@@ -152,8 +155,11 @@
flow.Close()
continue
}
+
+ discharges := c.prepareDischarges(ctx, flow.LocalID(), flow.RemoteID(), method, opts)
+
lastErr = nil
- fc := newFlowClient(flow)
+ fc := newFlowClient(flow, &c.dischargeCache, discharges)
if verr := fc.start(suffix, method, args, timeout, blessing); verr != nil {
return nil, verr
}
@@ -172,7 +178,7 @@
if server == nil {
return nil, fmt.Errorf("server identity cannot be nil")
}
- // TODO(ataly): Fetch third-party discharges from the server.
+ // TODO(ataly,andreser): Check the third-party discharges the server presents
// TODO(ataly): What should the label be for the context? Typically the label is the security.Label
// of the method but we don't have that information here at the client.
authID, err := server.Authorize(isecurity.NewContext(isecurity.ContextArgs{
@@ -236,16 +242,21 @@
flow stream.Flow // the underlying flow
response ipc.Response // each decoded response message is kept here
+ discharges []security.ThirdPartyDischarge // discharges used for this request
+ dischargeCache *dischargeCache // client-global discharge cache reference type
+
sendClosedMu sync.Mutex
sendClosed bool // is the send side already closed? GUARDED_BY(sendClosedMu)
}
-func newFlowClient(flow stream.Flow) *flowClient {
+func newFlowClient(flow stream.Flow, dischargeCache *dischargeCache, discharges []security.ThirdPartyDischarge) *flowClient {
return &flowClient{
// TODO(toddw): Support different codecs
- dec: vom.NewDecoder(flow),
- enc: vom.NewEncoder(flow),
- flow: flow,
+ dec: vom.NewDecoder(flow),
+ enc: vom.NewEncoder(flow),
+ flow: flow,
+ discharges: discharges,
+ dischargeCache: dischargeCache,
}
}
@@ -258,11 +269,12 @@
func (fc *flowClient) start(suffix, method string, args []interface{}, timeout time.Duration, blessing security.PublicID) verror.E {
req := ipc.Request{
- Suffix: suffix,
- Method: method,
- NumPosArgs: uint64(len(args)),
- Timeout: int64(timeout),
- HasBlessing: blessing != nil,
+ Suffix: suffix,
+ Method: method,
+ NumPosArgs: uint64(len(args)),
+ Timeout: int64(timeout),
+ HasBlessing: blessing != nil,
+ NumDischarges: uint64(len(fc.discharges)),
}
if err := fc.enc.Encode(req); err != nil {
return fc.close(verror.BadProtocolf("ipc: request encoding failed: %v", err))
@@ -272,6 +284,11 @@
return fc.close(verror.BadProtocolf("ipc: blessing encoding failed: %v", err))
}
}
+ for _, d := range fc.discharges {
+ if err := fc.enc.Encode(d); err != nil {
+ return fc.close(verror.BadProtocolf("ipc: failed to encode discharge for %x: %v", d.CaveatID(), err))
+ }
+ }
for ix, arg := range args {
if err := fc.enc.Encode(arg); err != nil {
return fc.close(verror.BadProtocolf("ipc: arg %d encoding failed: %v", ix, err))
@@ -374,6 +391,14 @@
}
}
if fc.response.Error != nil {
+ if verror.Is(fc.response.Error, verror.NotAuthorized) {
+ // In case the error was caused by a bad discharge, we do not want to get stuck
+ // with retrying again and again with this discharge. As there is no direct way
+ // 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.dischargeCache.Invalidate(fc.discharges...)
+ }
return fc.close(verror.ConvertWithDefault(verror.Internal, fc.response.Error))
}
if got, want := fc.response.NumPosResults, uint64(len(resultptrs)); got != want {
diff --git a/runtimes/google/ipc/discharges.go b/runtimes/google/ipc/discharges.go
new file mode 100644
index 0000000..75d99f3
--- /dev/null
+++ b/runtimes/google/ipc/discharges.go
@@ -0,0 +1,179 @@
+package ipc
+
+import (
+ "sync"
+ "veyron2"
+ "veyron2/context"
+ "veyron2/ipc"
+ "veyron2/security"
+ "veyron2/vdl"
+ "veyron2/vlog"
+)
+
+// prepareDischarges retrieves the caveat discharges required for using blessing
+// at server. The discharges are either found in the client cache, in the call
+// options, or requested from the discharge issuer indicated on the caveat.
+// Note that requesting a discharge is an ipc call, so one copy of this
+// function must be able to successfully terminate while another is blocked.
+func (c *client) prepareDischarges(ctx context.T, blessing, server security.PublicID,
+ method string, opts []ipc.CallOpt) (ret []security.ThirdPartyDischarge) {
+ // TODO(andreser,ataly): figure out whether this should return an error and how that should be handled
+ // Missing discharges do not necessarily mean the blessing is invalid (e.g., SetID)
+ if blessing == nil {
+ return
+ }
+
+ var caveats []security.ThirdPartyCaveat
+ for _, cav := range blessing.ThirdPartyCaveats() {
+ if server.Match(cav.Service) {
+ caveats = append(caveats, cav.Caveat.(security.ThirdPartyCaveat))
+ }
+ }
+ if len(caveats) == 0 {
+ return
+ }
+
+ discharges := make([]security.ThirdPartyDischarge, len(caveats))
+ dischargesFromOpts(caveats, opts, discharges)
+ c.dischargeCache.Discharges(caveats, discharges)
+ if shouldFetchDischarges(opts) {
+ c.fetchDischarges(ctx, caveats, opts, discharges)
+ }
+ for _, d := range discharges {
+ if d != nil {
+ ret = append(ret, d)
+ }
+ }
+ return
+}
+
+// dischargeCache is a concurrency-safe cache for third party caveat discharges.
+type dischargeCache struct {
+ sync.RWMutex
+ security.CaveatDischargeMap // GUARDED_BY(RWMutex)
+}
+
+// Add inserts the argument to the cache, possibly overwriting previous
+// discharges for the same caveat.
+func (dcc *dischargeCache) Add(discharges ...security.ThirdPartyDischarge) {
+ dcc.Lock()
+ for _, d := range discharges {
+ dcc.CaveatDischargeMap[d.CaveatID()] = d
+ }
+ dcc.Unlock()
+}
+
+// Invalidate removes discharges from the cache.
+func (dcc *dischargeCache) Invalidate(discharges ...security.ThirdPartyDischarge) {
+ dcc.Lock()
+ for _, d := range discharges {
+ if dcc.CaveatDischargeMap[d.CaveatID()] == d {
+ delete(dcc.CaveatDischargeMap, d.CaveatID())
+ }
+ }
+ dcc.Unlock()
+}
+
+// Discharges takes a slice of caveats and a slice of discharges of the same
+// length and fills in nil entries in the discharges slice with discharges
+// from the cache (if there are any).
+// REQUIRES: len(caveats) == len(out)
+func (dcc *dischargeCache) Discharges(caveats []security.ThirdPartyCaveat, out []security.ThirdPartyDischarge) {
+ dcc.Lock()
+ for i, d := range out {
+ if d != nil {
+ continue
+ }
+ out[i] = dcc.CaveatDischargeMap[caveats[i].ID()]
+ }
+ dcc.Unlock()
+}
+
+// dischargesFromOpts fills in the nils in the out argument with discharges in
+// opts that match the caveat at the same index in caveats.
+// REQUIRES: len(caveats) == len(out)
+func dischargesFromOpts(caveats []security.ThirdPartyCaveat, opts []ipc.CallOpt,
+ out []security.ThirdPartyDischarge) {
+ for _, opt := range opts {
+ d, ok := opt.(veyron2.DischargeOpt)
+ if !ok {
+ continue
+ }
+ for i, cav := range caveats {
+ if out[i] == nil && d.CaveatID() == cav.ID() {
+ out[i] = d
+ }
+ }
+ }
+}
+
+// fetchDischarges fills in out by fetching discharges for caveats from the
+// appropriate discharge service. Since there may be dependencies in the
+// caveats, fetchDischarges keeps retrying until either all discharges can be
+// fetched or no new discharges are fetched.
+// REQUIRES: len(caveats) == len(out)
+func (c *client) fetchDischarges(ctx context.T, caveats []security.ThirdPartyCaveat, opts []ipc.CallOpt, out []security.ThirdPartyDischarge) {
+ opts = append([]ipc.CallOpt{dontFetchDischarges{}}, opts...)
+ var wg sync.WaitGroup
+ for {
+ type fetched struct {
+ idx int
+ discharge security.ThirdPartyDischarge
+ }
+ discharges := make(chan fetched, len(caveats))
+ for i := range caveats {
+ if out[i] != nil {
+ continue
+ }
+ wg.Add(1)
+ go func(i int, cav security.ThirdPartyCaveat) {
+ defer wg.Done()
+ vlog.VI(3).Infof("Fetching discharge for %T from %v", cav.ID(), cav, cav.Location())
+ call, err := c.StartCall(ctx, cav.Location(), "Discharge", []interface{}{cav}, opts...)
+ if err != nil {
+ vlog.VI(3).Infof("Discharge fetch for caveat %T from %v failed: %v", cav, cav.Location(), err)
+ return
+ }
+ var dAny vdl.Any
+ // TODO(ashankar): Retry on errors like no-route-to-service, name resolution failures etc.
+ ierr := call.Finish(&dAny, &err)
+ if ierr != nil || err != nil {
+ vlog.VI(3).Infof("Discharge fetch for caveat %T from %v failed: (%v, %v)", cav, cav.Location(), err, ierr)
+ return
+ }
+ d, ok := dAny.(security.ThirdPartyDischarge)
+ if !ok {
+ vlog.Errorf("fetchDischarges: server at %s sent a %T (%v) instead of a ThirdPartyDischarge", cav.Location(), dAny, dAny)
+ }
+ discharges <- fetched{i, d}
+ }(i, caveats[i])
+ }
+ wg.Wait()
+ close(discharges)
+ var got int
+ for fetched := range discharges {
+ c.dischargeCache.Add(fetched.discharge)
+ out[fetched.idx] = fetched.discharge
+ got++
+ }
+ vlog.VI(2).Infof("fetchDischarges: got %d discharges", got)
+ if got == 0 {
+ return
+ }
+ }
+}
+
+// dontFetchDischares is an ipc.CallOpt that indicates that no extre ipc-s
+// should be done to fetch discharges for the call with this opt.
+// Discharges in the cache and in the call options are still used.
+type dontFetchDischarges struct{}
+
+func (dontFetchDischarges) IPCCallOpt() {}
+func shouldFetchDischarges(opts []ipc.CallOpt) bool {
+ for _, opt := range opts {
+ if _, ok := opt.(dontFetchDischarges); ok {
+ return false
+ }
+ }
+ return true
+}
diff --git a/runtimes/google/ipc/flow_test.go b/runtimes/google/ipc/flow_test.go
index 7009258..8850b21 100644
--- a/runtimes/google/ipc/flow_test.go
+++ b/runtimes/google/ipc/flow_test.go
@@ -117,7 +117,7 @@
ipcServer := &server{disp: testDisp{newEchoInvoker}}
for _, test := range tests {
clientFlow, serverFlow := newTestFlows()
- client := newFlowClient(clientFlow)
+ client := newFlowClient(clientFlow, nil, nil)
server := newFlowServer(serverFlow, ipcServer)
err := client.start(test.suffix, test.method, test.args, time.Duration(0), nil)
if err != nil {
diff --git a/runtimes/google/ipc/full_test.go b/runtimes/google/ipc/full_test.go
index 86b5a18..66e1cc7 100644
--- a/runtimes/google/ipc/full_test.go
+++ b/runtimes/google/ipc/full_test.go
@@ -30,17 +30,46 @@
"veyron2/ipc/stream"
"veyron2/naming"
"veyron2/security"
+ "veyron2/vdl"
"veyron2/verror"
"veyron2/vlog"
+ "veyron2/vom"
)
var (
+ errAuthorizer = errors.New("ipc: application Authorizer denied access")
errMethod = verror.Abortedf("server returned an error")
clientID security.PrivateID
serverID security.PrivateID
+ clock = new(fakeClock)
)
-var errAuthorizer = errors.New("ipc: application Authorizer denied access")
+type fakeClock struct {
+ sync.Mutex
+ time int
+}
+
+func (c *fakeClock) Now() int {
+ c.Lock()
+ defer c.Unlock()
+ return c.time
+}
+
+func (c *fakeClock) Advance(steps uint) {
+ c.Lock()
+ c.time += int(steps)
+ c.Unlock()
+}
+
+type fakeTimeCaveat int
+
+func (c fakeTimeCaveat) Validate(security.Context) error {
+ now := clock.Now()
+ if now > int(c) {
+ return fmt.Errorf("fakeTimeCaveat expired: now=%d > then=%d", now, c)
+ }
+ return nil
+}
type fakeContext struct{}
@@ -99,6 +128,18 @@
return "UnauthorizedResult", fmt.Errorf("Unauthorized should never be called")
}
+type dischargeServer struct{}
+
+func (*dischargeServer) Discharge(ctx ipc.ServerCall, caveat vdl.Any) (vdl.Any, error) {
+ c, ok := caveat.(security.ThirdPartyCaveat)
+ if !ok {
+ return nil, fmt.Errorf("discharger: unknown caveat(%T)", caveat)
+ }
+ // Add a fakeTimeCaveat to allow the discharge to expire
+ expiry := fakeTimeCaveat(clock.Now())
+ return serverID.MintDischarge(c, ctx, time.Hour, []security.ServiceCaveat{security.UniversalCaveat(expiry)})
+}
+
type testServerAuthorizer struct{}
func (testServerAuthorizer) Authorize(c security.Context) error {
@@ -113,18 +154,22 @@
func (t testServerDisp) Lookup(suffix string) (ipc.Invoker, security.Authorizer, error) {
// If suffix is "nilAuth" we use default authorization, if it is "aclAuth" we
// use an ACL based authorizer, and otherwise we use the custom testServerAuthorizer.
- if suffix == "nilAuth" {
- return ipc.ReflectInvoker(t.server), nil, nil
- }
- if suffix == "aclAuth" {
+ var authorizer security.Authorizer
+ switch suffix {
+ case "discharger":
+ return ipc.ReflectInvoker(&dischargeServer{}), testServerAuthorizer{}, nil
+ case "nilAuth":
+ authorizer = nil
+ case "aclAuth":
// Only authorize clients matching patterns "client" or "server/*".
- acl := security.ACL{
+ authorizer = security.NewACLAuthorizer(security.ACL{
"server/*": security.LabelSet(security.AdminLabel),
"client": security.LabelSet(security.AdminLabel),
- }
- return ipc.ReflectInvoker(t.server), security.NewACLAuthorizer(acl), nil
+ })
+ default:
+ authorizer = testServerAuthorizer{}
}
- return ipc.ReflectInvoker(t.server), testServerAuthorizer{}, nil
+ return ipc.ReflectInvoker(t.server), authorizer, nil
}
// namespace is a simple partial implementation of naming.Namespace. In
@@ -228,6 +273,9 @@
if err := server.Serve("mountpoint/server", disp); err != nil {
t.Errorf("server.Publish failed: %v", err)
}
+ if err := server.Serve("mountpoint/discharger", disp); err != nil {
+ t.Errorf("server.Publish for discharger failed: %v", err)
+ }
return ep, server
}
@@ -315,6 +363,27 @@
return derivedID
}
+// deriveForThirdPartyCaveats creates a SetPrivateID that can be used for
+// 1. talking to the server, if the caveats are fulfilled
+// 2. getting discharges, even if the caveats are not fulfilled
+// As an identity with an unfulfilled caveat is invalid (even for asking for a
+// discharge), this function creates a set of two identities. The first will
+// have the caveats, the second will always be valid, but only for getting
+// discharges. The client presents both blessings in both cases, the discharger
+// ignores the first if it is invalid.
+func deriveForThirdPartyCaveats(blessor security.PrivateID, name string, caveats ...security.ServiceCaveat) security.PrivateID {
+ id := derive(blessor, name, caveats...)
+ dischargeID, err := id.Derive(bless(blessor, id.PublicID(), name, security.UniversalCaveat(caveat.MethodRestriction{"Discharge"})))
+ if err != nil {
+ panic(err)
+ }
+ id, err = isecurity.NewSetPrivateID(id, dischargeID)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
func matchesErrorPattern(err error, pattern string) bool {
if (len(pattern) == 0) != (err == nil) {
return false
@@ -370,20 +439,23 @@
}
func TestStartCall(t *testing.T) {
- authorizeErr := "not authorized because"
- nameErr := "does not match the provided pattern"
+ const (
+ authorizeErr = "not authorized because"
+ nameErr = "does not match the provided pattern"
+ )
+ var (
+ now = time.Now()
+ cavOnlyV1 = security.UniversalCaveat(caveat.PeerIdentity{"client/v1"})
+ cavExpired = security.ServiceCaveat{
+ Service: security.AllPrincipals,
+ Caveat: &caveat.Expiry{IssueTime: now, ExpiryTime: now},
+ }
- cavOnlyV1 := security.UniversalCaveat(caveat.PeerIdentity{"client/v1"})
- now := time.Now()
- cavExpired := security.ServiceCaveat{
- Service: security.AllPrincipals,
- Caveat: &caveat.Expiry{IssueTime: now, ExpiryTime: now},
- }
-
- clientV1ID := derive(clientID, "v1")
- clientV2ID := derive(clientID, "v2")
- serverV1ID := derive(serverID, "v1", cavOnlyV1)
- serverExpiredID := derive(serverID, "expired", cavExpired)
+ clientV1ID = derive(clientID, "v1")
+ clientV2ID = derive(clientID, "v2")
+ serverV1ID = derive(serverID, "v1", cavOnlyV1)
+ serverExpiredID = derive(serverID, "expired", cavExpired)
+ )
tests := []struct {
clientID, serverID security.PrivateID
@@ -546,21 +618,46 @@
}
}
+func mkThirdPartyCaveat(discharger security.PublicID, location string, c security.Caveat) security.ThirdPartyCaveat {
+ tpc, err := caveat.NewPublicKeyCaveat(c, discharger, location)
+ if err != nil {
+ panic(err)
+ }
+ return tpc
+}
+
func TestRPCAuthorization(t *testing.T) {
- cavOnlyEcho := security.ServiceCaveat{
- Service: security.AllPrincipals,
- Caveat: caveat.MethodRestriction{"Echo"},
- }
- now := time.Now()
- cavExpired := security.ServiceCaveat{
- Service: security.AllPrincipals,
- Caveat: &caveat.Expiry{IssueTime: now, ExpiryTime: now},
- }
+ var (
+ now = time.Now()
+ // First-party caveats
+ cavOnlyEcho = security.ServiceCaveat{
+ Service: security.AllPrincipals,
+ Caveat: caveat.MethodRestriction{"Echo"},
+ }
+ cavExpired = security.ServiceCaveat{
+ Service: security.AllPrincipals,
+ Caveat: &caveat.Expiry{IssueTime: now, ExpiryTime: now},
+ }
+ // Third-party caveats
+ // The Discharge service can be run by any identity, but in our tests the same server runs
+ // a Discharge service as well.
+ dischargerID = serverID.PublicID()
+ cavTPValid = security.ServiceCaveat{
+ Service: security.PrincipalPattern(serverID.PublicID().Names()[0]),
+ Caveat: mkThirdPartyCaveat(dischargerID, "mountpoint/server/discharger", &caveat.Expiry{ExpiryTime: now.Add(24 * time.Hour)}),
+ }
+ cavTPExpired = security.ServiceCaveat{
+ Service: security.PrincipalPattern(serverID.PublicID().Names()[0]),
+ Caveat: mkThirdPartyCaveat(dischargerID, "mountpoint/server/discharger", &caveat.Expiry{IssueTime: now, ExpiryTime: now}),
+ }
- blessedByServerOnlyEcho := derive(serverID, "onlyEcho", cavOnlyEcho)
- blessedByServerExpired := derive(serverID, "expired", cavExpired)
- blessedByClient := derive(clientID, "blessed")
-
+ // Client blessings that will be tested
+ blessedByServerOnlyEcho = derive(serverID, "onlyEcho", cavOnlyEcho)
+ blessedByServerExpired = derive(serverID, "expired", cavExpired)
+ blessedByServerTPValid = deriveForThirdPartyCaveats(serverID, "tpvalid", cavTPValid)
+ blessedByServerTPExpired = deriveForThirdPartyCaveats(serverID, "tpexpired", cavTPExpired)
+ blessedByClient = derive(clientID, "blessed")
+ )
const (
expiredIDErr = "forbids credential from being used at this time"
aclAuthErr = "no matching ACL entry found"
@@ -599,7 +696,6 @@
{clientID, "mountpoint/server/aclAuth", "Closure", nil, nil, ""},
{blessedByClient, "mountpoint/server/aclAuth", "Closure", nil, nil, aclAuthErr},
{serverID, "mountpoint/server/aclAuth", "Closure", nil, nil, ""},
-
// All methods except "Unauthorized" are authorized by the custom authorizer.
{clientID, "mountpoint/server/suffix", "Echo", v{"foo"}, v{`method:"Echo",suffix:"suffix",arg:"foo"`}, ""},
{blessedByClient, "mountpoint/server/suffix", "Echo", v{"foo"}, v{`method:"Echo",suffix:"suffix",arg:"foo"`}, ""},
@@ -611,6 +707,9 @@
{clientID, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, "application Authorizer denied access"},
{blessedByClient, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, "application Authorizer denied access"},
{serverID, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, "application Authorizer denied access"},
+ // Third-party caveat discharges should be fetched and forwarded
+ {blessedByServerTPValid, "mountpoint/server/suffix", "Echo", v{"foo"}, v{`method:"Echo",suffix:"suffix",arg:"foo"`}, ""},
+ {blessedByServerTPExpired, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, "missing discharge"},
}
name := func(t testcase) string {
return fmt.Sprintf("%q RPCing %s.%s(%v)", t.clientID.PublicID(), t.name, t.method, t.args)
@@ -640,6 +739,49 @@
}
}
+type alwaysValidCaveat struct{}
+
+func (alwaysValidCaveat) Validate(security.Context) error { return nil }
+
+func TestDischargePurgeFromCache(t *testing.T) {
+ var (
+ dischargerID = serverID.PublicID()
+ caveat = mkThirdPartyCaveat(dischargerID, "mountpoint/server/discharger", alwaysValidCaveat{})
+ clientCID = deriveForThirdPartyCaveats(serverID, "client", security.UniversalCaveat(caveat))
+ )
+ b := createBundle(t, clientCID, serverID, &testServer{})
+ defer b.cleanup(t)
+
+ call := func() error {
+ call, err := b.client.StartCall(&fakeContext{}, "mountpoint/server/suffix", "Echo", []interface{}{"batman"})
+ if err != nil {
+ return fmt.Errorf("client.StartCall failed: %v", err)
+ }
+ var got string
+ if err := call.Finish(&got); err != nil {
+ return fmt.Errorf("client.Finish failed: %v", err)
+ }
+ if want := `method:"Echo",suffix:"suffix",arg:"batman"`; got != want {
+ return fmt.Errorf("Got [%v] want [%v]", got, want)
+ }
+ return nil
+ }
+
+ // First call should succeed
+ if err := call(); err != nil {
+ t.Fatal(err)
+ }
+ // Advance virtual clock, which will invalidate the discharge
+ clock.Advance(1)
+ if err := call(); !matchesErrorPattern(err, "fakeTimeCaveat expired") {
+ t.Errorf("Got error [%v] wanted to match pattern 'fakeTimeCaveat expired'", err)
+ }
+ // But retrying will succeed since the discharge should be purged from cache and refreshed
+ if err := call(); err != nil {
+ t.Fatal(err)
+ }
+}
+
type cancelTestServer struct {
started chan struct{}
cancelled chan struct{}
@@ -1095,4 +1237,6 @@
blackbox.CommandTable["runServer"] = runServer
blackbox.CommandTable["runProxy"] = runProxy
+
+ vom.Register(fakeTimeCaveat(0))
}
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index f162fdf..1a88570 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -332,9 +332,10 @@
server: server,
disp: server.disp,
// TODO(toddw): Support different codecs
- dec: vom.NewDecoder(flow),
- enc: vom.NewEncoder(flow),
- flow: flow,
+ dec: vom.NewDecoder(flow),
+ enc: vom.NewEncoder(flow),
+ flow: flow,
+ discharges: make(security.CaveatDischargeMap),
}
}
@@ -450,7 +451,14 @@
// should servers be able to assume that a blessing is something that does not
// have the authorizations that the server's own identity has?
}
-
+ // Receive third party caveat discharges the client sent
+ for i := uint64(0); i < req.NumDischarges; i++ {
+ var d security.ThirdPartyDischarge
+ if err := fs.dec.Decode(&d); err != nil {
+ return nil, verror.BadProtocolf("ipc: decoding discharge %d of %d failed: %v", i, req.NumDischarges, err)
+ }
+ fs.discharges[d.CaveatID()] = d
+ }
// Lookup the invoker.
invoker, auth, suffix, verr := fs.lookup(req.Suffix)
fs.suffix = suffix // with leading /'s stripped
@@ -461,7 +469,6 @@
numArgs := int(req.NumPosArgs)
argptrs, label, err := invoker.Prepare(req.Method, numArgs)
fs.label = label
- // TODO(ataly, ashankar): Populate the "discharges" field from the request object req.
if err != nil {
return nil, verror.Makef(verror.ErrorID(err), "%s: name: %q", err, req.Suffix)
}