Merge "veyron/runtimes/google/ipc: Move "publisher" to its own package."
diff --git a/examples/rockpaperscissors/impl/impl_test.go b/examples/rockpaperscissors/impl/impl_test.go
index 883b6f8..c8dbf8d 100644
--- a/examples/rockpaperscissors/impl/impl_test.go
+++ b/examples/rockpaperscissors/impl/impl_test.go
@@ -78,27 +78,31 @@
rpsService, rpsStop := startRockPaperScissors(t, runtime, mtAddress)
defer rpsStop()
- if err := rpsService.Player().InitiateGame(); err != nil {
- t.Errorf("Failed to initiate game: %v", err)
+ const numGames = 10
+ for x := 0; x < numGames; x++ {
+ if err := rpsService.Player().InitiateGame(); err != nil {
+ t.Errorf("Failed to initiate game: %v", err)
+ }
}
+ rpsService.Player().WaitUntilIdle()
- // There was only one game, but the player played twice. So, we
- // expected the player to show that it played 2 games, and won 1.
+ // For each game, the player plays twice. So, we expect the player to
+ // show that it played 2×numGames, and won numGames.
played, won := rpsService.Player().Stats()
- if want, got := int64(2), played; want != got {
+ if want, got := int64(2*numGames), played; want != got {
t.Errorf("Unexpected number of played games. Got %d, want %d", got, want)
}
- if want, got := int64(1), won; want != got {
+ if want, got := int64(numGames), won; want != got {
t.Errorf("Unexpected number of won games. Got %d, want %d", got, want)
}
- // The Judge ran exactly one game.
- if want, got := int64(1), rpsService.Judge().Stats(); want != got {
+ // The Judge ran every game.
+ if want, got := int64(numGames), rpsService.Judge().Stats(); want != got {
t.Errorf("Unexpected number of games run. Got %d, want %d", got, want)
}
- // The Score Keeper received one score card.
- if want, got := int64(1), rpsService.ScoreKeeper().Stats(); want != got {
+ // The Score Keeper received one score card per game.
+ if want, got := int64(numGames), rpsService.ScoreKeeper().Stats(); want != got {
t.Errorf("Unexpected number of score cards. Got %d, want %d", got, want)
}
}
diff --git a/examples/rockpaperscissors/impl/player.go b/examples/rockpaperscissors/impl/player.go
index fa7ee3a..1416f41 100644
--- a/examples/rockpaperscissors/impl/player.go
+++ b/examples/rockpaperscissors/impl/player.go
@@ -16,10 +16,11 @@
)
type Player struct {
- mt naming.MountTable
- lock sync.Mutex
- gamesPlayed common.Counter
- gamesWon common.Counter
+ mt naming.MountTable
+ lock sync.Mutex
+ gamesPlayed common.Counter
+ gamesWon common.Counter
+ gamesInProgress common.Counter
}
func NewPlayer(mt naming.MountTable) *Player {
@@ -32,6 +33,13 @@
return
}
+// only used by tests.
+func (p *Player) WaitUntilIdle() {
+ for p.gamesInProgress.Value() != 0 {
+ time.Sleep(10 * time.Millisecond)
+ }
+}
+
func (p *Player) InitiateGame() error {
judge, err := common.FindJudge(p.mt)
if err != nil {
@@ -99,6 +107,8 @@
// playGame plays an entire game, which really only consists of reading
// commands from the server, and picking a random "move" when asked to.
func (p *Player) playGame(judge string, gameID rps.GameID) (rps.PlayResult, error) {
+ p.gamesInProgress.Add(1)
+ defer p.gamesInProgress.Add(-1)
j, err := rps.BindRockPaperScissors(judge)
if err != nil {
return rps.PlayResult{}, err
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index 16a711c..d974020 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -1,6 +1,7 @@
package ipc
import (
+ "fmt"
"io"
"sync"
"time"
@@ -141,32 +142,15 @@
}
// Validate caveats on the server's identity for the context associated with this call.
- remoteID := flow.RemoteID()
- if remoteID == nil {
- lastErr = verror.NotAuthorizedf("ipc: server identity cannot be nil")
- continue
- }
- // TODO(ataly): Fetch third-party discharges from the server.
- // 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.
- authorizedRemoteID, err := remoteID.Authorize(isecurity.NewContext(
- isecurity.ContextArgs{
- LocalID: flow.LocalID(),
- RemoteID: remoteID,
- }))
+ blessing, err := authorizeServer(flow.LocalID(), flow.RemoteID(), opts)
if err != nil {
- lastErr = verror.NotAuthorizedf("ipc: server identity %q has one or more invalid caveats: %v", remoteID, err)
+ lastErr = verror.NotAuthorizedf("ipc: client unwilling to talk to server %q: %v", flow.RemoteID(), err)
+ flow.Close()
continue
}
-
- if lastErr = matchServerID(authorizedRemoteID, opts); lastErr != nil {
- continue
- }
-
- // remoteID is authorized for the context associated with this call.
lastErr = nil
fc := newFlowClient(flow)
- if verr := fc.start(suffix, method, args, timeout); verr != nil {
+ if verr := fc.start(suffix, method, args, timeout, blessing); verr != nil {
return nil, verr
}
return fc, nil
@@ -177,6 +161,45 @@
return nil, errNoServers
}
+// authorizeServer validates that server has an identity that the client is willing to converse
+// with, and if so returns a blessing to be provided to the server. This blessing can be nil,
+// which indicates that the client does wish to talk to the server but not provide any blessings.
+func authorizeServer(client, server security.PublicID, opts []ipc.CallOpt) (security.PublicID, error) {
+ if server == nil {
+ return nil, fmt.Errorf("server identity cannot be nil")
+ }
+ // TODO(ataly): Fetch third-party discharges from the server.
+ // 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{
+ LocalID: client,
+ RemoteID: server,
+ }))
+ if err != nil {
+ return nil, err
+ }
+ var granter ipc.Granter
+ for _, o := range opts {
+ switch v := o.(type) {
+ case veyron2.RemoteID:
+ if !authID.Match(security.PrincipalPattern(v)) {
+ return nil, fmt.Errorf("server %q does not match the provided pattern %q", authID, v)
+ }
+ case ipc.Granter:
+ // Later Granters take precedence over earlier ones.
+ // Or should fail if there are multiple provided?
+ granter = v
+ }
+ }
+ var blessing security.PublicID
+ if granter != nil {
+ if blessing, err = granter.Grant(authID); err != nil {
+ return nil, fmt.Errorf("failed to grant credentials to server %q: %v", authID, err)
+ }
+ }
+ return blessing, nil
+}
+
func (c *client) getCallTimeout(opts []ipc.CallOpt) time.Duration {
timeout := c.callTimeout
for _, opt := range opts {
@@ -229,16 +252,22 @@
return verr
}
-func (fc *flowClient) start(suffix, method string, args []interface{}, timeout time.Duration) verror.E {
+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),
+ Suffix: suffix,
+ Method: method,
+ NumPosArgs: uint64(len(args)),
+ Timeout: int64(timeout),
+ HasBlessing: blessing != nil,
}
if err := fc.enc.Encode(req); err != nil {
return fc.close(verror.BadProtocolf("ipc: request encoding failed: %v", err))
}
+ if blessing != nil {
+ if err := fc.enc.Encode(blessing); err != nil {
+ return fc.close(verror.BadProtocolf("ipc: blessing encoding failed: %v", 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))
@@ -357,12 +386,3 @@
func (fc *flowClient) Cancel() {
fc.flow.Cancel()
}
-
-func matchServerID(id security.PublicID, opts []ipc.CallOpt) verror.E {
- for _, opt := range opts {
- if pattern, ok := opt.(veyron2.RemoteID); ok && !id.Match(security.PrincipalPattern(pattern)) {
- return verror.NotAuthorizedf("ipc: server identity %q does not have a name matching the provided pattern %q", id, pattern)
- }
- }
- return nil
-}
diff --git a/runtimes/google/ipc/flow_test.go b/runtimes/google/ipc/flow_test.go
index 0a60b99..35f7662 100644
--- a/runtimes/google/ipc/flow_test.go
+++ b/runtimes/google/ipc/flow_test.go
@@ -122,7 +122,7 @@
clientFlow, serverFlow := newTestFlows()
client := newFlowClient(clientFlow)
server := newFlowServer(serverFlow, ipcServer)
- err := client.start(test.suffix, test.method, test.args, time.Duration(0))
+ err := client.start(test.suffix, test.method, test.args, time.Duration(0), nil)
if err != nil {
t.Errorf("%s client.start unexpected error: %v", name(test), err)
}
diff --git a/runtimes/google/ipc/full_test.go b/runtimes/google/ipc/full_test.go
index 39964a6..5858e11 100644
--- a/runtimes/google/ipc/full_test.go
+++ b/runtimes/google/ipc/full_test.go
@@ -64,6 +64,10 @@
return fmt.Sprintf("%v", call.LocalID()), fmt.Sprintf("%v", call.RemoteID())
}
+func (*testServer) EchoBlessing(call ipc.ServerCall, arg string) (result, blessing string) {
+ return arg, fmt.Sprintf("%v", call.Blessing())
+}
+
func (*testServer) EchoAndError(call ipc.ServerCall, arg string) (string, error) {
result := fmt.Sprintf("method:%q,suffix:%q,arg:%q", call.Method(), call.Suffix(), arg)
if arg == "error" {
@@ -279,16 +283,20 @@
return
}
+func bless(blessor security.PrivateID, blessee security.PublicID, name string, caveats ...security.ServiceCaveat) security.PublicID {
+ blessed, err := blessor.Bless(blessee, name, 24*time.Hour, caveats)
+ if err != nil {
+ panic(err)
+ }
+ return blessed
+}
+
func derive(blessor security.PrivateID, name string, caveats ...security.ServiceCaveat) security.PrivateID {
id, err := isecurity.NewPrivateID("irrelevant")
if err != nil {
panic(err)
}
- blessedID, err := blessor.Bless(id.PublicID(), name, 5*time.Minute, caveats)
- if err != nil {
- panic(err)
- }
- derivedID, err := id.Derive(blessedID)
+ derivedID, err := id.Derive(bless(blessor, id.PublicID(), name, caveats...))
if err != nil {
panic(err)
}
@@ -303,8 +311,8 @@
}
func TestStartCall(t *testing.T) {
- authorizeErr := "has one or more invalid caveats"
- nameErr := "does not have a name matching the provided pattern"
+ authorizeErr := "not authorized because"
+ nameErr := "does not match the provided pattern"
cavOnlyV1 := security.UniversalCaveat(caveat.PeerIdentity{"client/v1"})
now := time.Now()
@@ -436,6 +444,49 @@
}
}
+// granter implements ipc.Granter, returning a fixed (security.PublicID, error) pair.
+type granter struct {
+ ipc.CallOpt
+ id security.PublicID
+ err error
+}
+
+func (g granter) Grant(id security.PublicID) (security.PublicID, error) { return g.id, g.err }
+
+func TestBlessing(t *testing.T) {
+ b := createBundle(t, clientID, serverID, &testServer{})
+ defer b.cleanup(t)
+
+ tests := []struct {
+ granter ipc.CallOpt
+ blessing, starterr, finisherr string
+ }{
+ {blessing: "<nil>"},
+ {granter: granter{id: bless(clientID, serverID.PublicID(), "blessed")}, blessing: "client/blessed"},
+ {granter: granter{err: errors.New("hell no")}, starterr: "hell no"},
+ {granter: granter{id: clientID.PublicID()}, finisherr: "blessing provided not bound to this server"},
+ }
+ for _, test := range tests {
+ call, err := b.client.StartCall(&fakeContext{}, "mountpoint/server/suffix", "EchoBlessing", []interface{}{"argument"}, test.granter)
+ if !matchesErrorPattern(err, test.starterr) {
+ t.Errorf("%+v: StartCall returned error %v", test, err)
+ }
+ if err != nil {
+ continue
+ }
+ var result, blessing string
+ if err = call.Finish(&result, &blessing); !matchesErrorPattern(err, test.finisherr) {
+ t.Errorf("%+v: Finish returned error %v", test, err)
+ }
+ if err != nil {
+ continue
+ }
+ if result != "argument" || blessing != test.blessing {
+ t.Errorf("%+v: Got (%q, %q)", test, result, blessing)
+ }
+ }
+}
+
func TestRPCAuthorization(t *testing.T) {
cavOnlyEcho := security.ServiceCaveat{
Service: security.AllPrincipals,
diff --git a/runtimes/google/ipc/jni/arg_getter_test.go b/runtimes/google/ipc/jni/arg_getter_test.go
index 4694ab6..f931770 100644
--- a/runtimes/google/ipc/jni/arg_getter_test.go
+++ b/runtimes/google/ipc/jni/arg_getter_test.go
@@ -19,7 +19,7 @@
func compareTypes(t *testing.T, method string, got, want []interface{}, argKind string) {
if len(got) != len(want) {
- t.Errorf("mismatch in input arguments: got %v , want %v ", got, want)
+ t.Errorf("mismatch in input arguments: got %v , want %v ", got, want)
}
for i, _ := range got {
compareType(t, method, got[i], want[i], argKind)
@@ -35,12 +35,12 @@
if got, want := getter.vdlPath, iface; got != want {
t.Errorf("invalid pathname: got %v , want %v ", got, want)
}
- data := []struct{
- Method string
- NumInArgs int
- in, out []interface{}
+ data := []struct {
+ Method string
+ NumInArgs int
+ in, out []interface{}
sSend, sRecv interface{}
- sFinish []interface{}
+ sFinish []interface{}
}{
{"MethodA1", 0, nil, nil, nil, nil, nil},
{"MethodA2", 2, []interface{}{(*int32)(nil), (*string)(nil)}, []interface{}{(*string)(nil)}, nil, nil, nil},
@@ -61,4 +61,4 @@
compareType(t, d.Method, m.StreamRecvPtr(), d.sRecv, "stream recv")
compareTypes(t, d.Method, m.StreamFinishPtrs(), d.sFinish, "stream finish")
}
-}
\ No newline at end of file
+}
diff --git a/runtimes/google/ipc/server.go b/runtimes/google/ipc/server.go
index ffa9a11..00d1290 100644
--- a/runtimes/google/ipc/server.go
+++ b/runtimes/google/ipc/server.go
@@ -4,6 +4,7 @@
"fmt"
"io"
"net"
+ "reflect"
"strings"
"sync"
"time"
@@ -246,6 +247,7 @@
// authorizedRemoteID is the PublicID obtained after authorizing the remoteID
// of the underlying flow for the current request context.
authorizedRemoteID security.PublicID
+ blessing security.PublicID
method, name, suffix string
label security.Label
discharges security.CaveatDischargeMap
@@ -364,6 +366,20 @@
return nil, verr
}
}
+ // If additional credentials are provided, make them available in the context
+ if req.HasBlessing {
+ if err := fs.dec.Decode(&fs.blessing); err != nil {
+ return nil, verror.BadProtocolf("ipc: blessing decoding failed: %v", err)
+ }
+ // Detect unusable blessings now, rather then discovering they are unusable on first use.
+ if !reflect.DeepEqual(fs.blessing.PublicKey(), fs.flow.LocalID().PublicKey()) {
+ return nil, verror.BadProtocolf("ipc: blessing provided not bound to this server")
+ }
+ // TODO(ashankar,ataly): Potential confused deputy attack: The client provides the
+ // server's identity as the blessing. Figure out what we want to do about this -
+ // should servers be able to assume that a blessing is something that does not
+ // have the authorizations that the server's own identity has?
+ }
// Lookup the invoker.
invoker, auth, name, suffix, verr := fs.lookup(req.Suffix)
fs.name = name
@@ -485,6 +501,7 @@
func (fs *flowServer) LocalID() security.PublicID { return fs.flow.LocalID() }
func (fs *flowServer) RemoteID() security.PublicID { return fs.authorizedRemoteID }
func (fs *flowServer) Deadline() time.Time { return fs.deadline }
+func (fs *flowServer) Blessing() security.PublicID { return fs.blessing }
func (fs *flowServer) LocalAddr() net.Addr { return fs.flow.LocalAddr() }
func (fs *flowServer) RemoteAddr() net.Addr { return fs.flow.RemoteAddr() }
diff --git a/services/store/memstore/blackbox/util.go b/services/store/memstore/blackbox/util.go
index cc2c921..9a4af7c 100644
--- a/services/store/memstore/blackbox/util.go
+++ b/services/store/memstore/blackbox/util.go
@@ -62,6 +62,10 @@
return rootPublicID
}
+func (rootContext) Blessing() security.PublicID {
+ return nil
+}
+
func (rootContext) LocalAddr() net.Addr {
return nil
}
diff --git a/services/store/memstore/util_test.go b/services/store/memstore/util_test.go
index 261356e..0917a4a 100644
--- a/services/store/memstore/util_test.go
+++ b/services/store/memstore/util_test.go
@@ -56,6 +56,10 @@
return rootPublicID
}
+func (*cancellableContext) Blessing() security.PublicID {
+ return nil
+}
+
func (*cancellableContext) LocalAddr() net.Addr {
return nil
}
diff --git a/services/store/memstore/watch/test_util.go b/services/store/memstore/watch/test_util.go
index 5f10875..ac34f91 100644
--- a/services/store/memstore/watch/test_util.go
+++ b/services/store/memstore/watch/test_util.go
@@ -78,6 +78,10 @@
return nil
}
+func (rootContext) Blessing() security.PublicID {
+ return nil
+}
+
type cancellableContext struct {
rootContext
diff --git a/services/store/server/server_test.go b/services/store/server/server_test.go
index 4f26b36..8acc8d4 100644
--- a/services/store/server/server_test.go
+++ b/services/store/server/server_test.go
@@ -67,6 +67,10 @@
return ctx.id
}
+func (*testContext) Blessing() security.PublicID {
+ return nil
+}
+
func (*testContext) LocalAddr() net.Addr {
return nil
}