| // 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 test |
| |
| import ( |
| "crypto/rand" |
| "encoding/hex" |
| "errors" |
| "fmt" |
| "io" |
| "net" |
| "reflect" |
| "strings" |
| "testing" |
| "time" |
| |
| "golang.org/x/crypto/nacl/box" |
| |
| "v.io/v23" |
| "v.io/v23/context" |
| "v.io/v23/flow" |
| "v.io/v23/flow/message" |
| "v.io/v23/i18n" |
| "v.io/v23/naming" |
| "v.io/v23/options" |
| "v.io/v23/rpc" |
| "v.io/v23/rpc/version" |
| "v.io/v23/security" |
| "v.io/v23/security/access" |
| "v.io/v23/vdl" |
| "v.io/v23/verror" |
| "v.io/v23/vom" |
| "v.io/v23/vtrace" |
| "v.io/x/lib/ibe" |
| "v.io/x/lib/netstate" |
| vsecurity "v.io/x/ref/lib/security" |
| "v.io/x/ref/lib/security/bcrypter" |
| "v.io/x/ref/runtime/internal/flow/crypto" |
| "v.io/x/ref/runtime/protocols/debug" |
| "v.io/x/ref/runtime/protocols/lib/tcputil" |
| "v.io/x/ref/test" |
| "v.io/x/ref/test/testutil" |
| ) |
| |
| func TestAddRemoveName(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| |
| _, s, err := v23.WithNewServer(ctx, "one", &testServer{}, nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| waitForNames(t, ctx, true, "one") |
| s.AddName("two") |
| s.AddName("three") |
| waitForNames(t, ctx, true, "one", "two", "three") |
| s.RemoveName("one") |
| waitForNames(t, ctx, false, "one") |
| s.RemoveName("two") |
| s.RemoveName("three") |
| waitForNames(t, ctx, false, "one", "two", "three") |
| } |
| |
| func TestCallWithNilContext(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| call, err := v23.GetClient(ctx).StartCall(nil, "foo", "bar", []interface{}{}) |
| if call != nil { |
| t.Errorf("Expected nil interface got: %#v", call) |
| } |
| if verror.ErrorID(err) != verror.ErrBadArg.ID { |
| t.Errorf("Expected a BadArg error, got: %s", err.Error()) |
| } |
| } |
| |
| func TestRPC(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| ctx = v23.WithListenSpec(ctx, rpc.ListenSpec{ |
| Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}, |
| }) |
| testRPC(t, ctx, true) |
| } |
| |
| func TestRPCWithWebsocket(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| ctx = v23.WithListenSpec(ctx, rpc.ListenSpec{ |
| Addrs: rpc.ListenAddrs{{"ws", "127.0.0.1:0"}}, |
| }) |
| testRPC(t, ctx, true) |
| } |
| |
| // TestCloseSendOnFinish tests that Finish informs the server that no more |
| // inputs will be sent by the client if CloseSend has not already done so. |
| func TestRPCCloseSendOnFinish(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| ctx = v23.WithListenSpec(ctx, rpc.ListenSpec{ |
| Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}, |
| }) |
| testRPC(t, ctx, false) |
| } |
| |
| func TestRPCCloseSendOnFinishWithWebsocket(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| ctx = v23.WithListenSpec(ctx, rpc.ListenSpec{ |
| Addrs: rpc.ListenAddrs{{"ws", "127.0.0.1:0"}}, |
| }) |
| testRPC(t, ctx, false) |
| } |
| |
| func testRPC(t *testing.T, ctx *context.T, shouldCloseSend bool) { |
| ctx = i18n.WithLangID(ctx, "foolang") |
| type v []interface{} |
| type testcase struct { |
| name string |
| method string |
| args v |
| streamArgs v |
| startErr error |
| results v |
| finishErr error |
| } |
| var ( |
| tests = []testcase{ |
| {"mountpoint/server/suffix", "Closure", nil, nil, nil, nil, nil}, |
| {"mountpoint/server/suffix", "Error", nil, nil, nil, nil, errMethod}, |
| |
| {"mountpoint/server/suffix", "Echo", v{"foo"}, nil, nil, v{`method:"Echo",suffix:"suffix",arg:"foo"`}, nil}, |
| {"mountpoint/server/suffix/abc", "Echo", v{"bar"}, nil, nil, v{`method:"Echo",suffix:"suffix/abc",arg:"bar"`}, nil}, |
| |
| {"mountpoint/server/suffix", "EchoUser", v{"foo", userType("bar")}, nil, nil, v{`method:"EchoUser",suffix:"suffix",arg:"foo"`, userType("bar")}, nil}, |
| {"mountpoint/server/suffix/abc", "EchoUser", v{"baz", userType("bla")}, nil, nil, v{`method:"EchoUser",suffix:"suffix/abc",arg:"baz"`, userType("bla")}, nil}, |
| {"mountpoint/server/suffix", "Stream", v{"foo"}, v{userType("bar"), userType("baz")}, nil, v{`method:"Stream",suffix:"suffix",arg:"foo" bar baz`}, nil}, |
| {"mountpoint/server/suffix/abc", "Stream", v{"123"}, v{userType("456"), userType("789")}, nil, v{`method:"Stream",suffix:"suffix/abc",arg:"123" 456 789`}, nil}, |
| {"mountpoint/server/suffix", "EchoBlessings", nil, nil, nil, v{"[test-blessing:server]", "[test-blessing:client]"}, nil}, |
| {"mountpoint/server/suffix", "EchoAndError", v{"bugs bunny"}, nil, nil, v{`method:"EchoAndError",suffix:"suffix",arg:"bugs bunny"`}, nil}, |
| {"mountpoint/server/suffix", "EchoAndError", v{"error"}, nil, nil, nil, errMethod}, |
| {"mountpoint/server/suffix", "EchoLang", nil, nil, nil, v{"foolang"}, nil}, |
| } |
| name = func(t testcase) string { |
| return fmt.Sprintf("%s.%s(%v)", t.name, t.method, t.args) |
| } |
| cctx = withPrincipal(t, ctx, "client") |
| sctx = withPrincipal(t, ctx, "server") |
| ) |
| _, _, err := v23.WithNewDispatchingServer(sctx, "mountpoint/server", &testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| client := v23.GetClient(cctx) |
| for _, test := range tests { |
| cctx.VI(1).Infof("%s client.StartCall", name(test)) |
| call, err := client.StartCall(cctx, test.name, test.method, test.args) |
| if err != test.startErr { |
| t.Errorf(`%s client.StartCall got error "%v", want "%v"`, |
| name(test), err, test.startErr) |
| continue |
| } |
| for _, sarg := range test.streamArgs { |
| cctx.VI(1).Infof("%s client.Send(%v)", name(test), sarg) |
| if err := call.Send(sarg); err != nil { |
| t.Errorf(`%s call.Send(%v) got unexpected error "%v"`, name(test), sarg, err) |
| } |
| var u userType |
| if err := call.Recv(&u); err != nil { |
| t.Errorf(`%s call.Recv(%v) got unexpected error "%v"`, name(test), sarg, err) |
| } |
| if !reflect.DeepEqual(u, sarg) { |
| t.Errorf("%s call.Recv got value %v, want %v", name(test), u, sarg) |
| } |
| } |
| if shouldCloseSend { |
| cctx.VI(1).Infof("%s call.CloseSend", name(test)) |
| // When the method does not involve streaming |
| // arguments, the server gets all the arguments in |
| // StartCall and then sends a response without |
| // (unnecessarily) waiting for a CloseSend message from |
| // the client. If the server responds before the |
| // CloseSend call is made at the client, the CloseSend |
| // call will fail. Thus, only check for errors on |
| // CloseSend if there are streaming arguments to begin |
| // with (i.e., only if the server is expected to wait |
| // for the CloseSend notification). |
| if err := call.CloseSend(); err != nil && len(test.streamArgs) > 0 { |
| t.Errorf(`%s call.CloseSend got unexpected error "%v"`, name(test), err) |
| } |
| } |
| cctx.VI(1).Infof("%s client.Finish", name(test)) |
| results := makeResultPtrs(test.results) |
| err = call.Finish(results...) |
| if got, want := err, test.finishErr; (got == nil) != (want == nil) { |
| t.Errorf(`%s call.Finish got error "%v", want "%v'`, name(test), got, want) |
| } else if want != nil && verror.ErrorID(got) != verror.ErrorID(want) { |
| t.Errorf(`%s call.Finish got error "%v", want "%v"`, name(test), got, want) |
| } |
| checkResultPtrs(t, name(test), results, test.results) |
| |
| // Calling Finish a second time should result in a useful error. |
| err = call.Finish(results...) |
| if !matchesErrorPattern(err, verror.ErrBadState, "FinishAlreadyCalled") { |
| t.Fatalf(`got "%v", want "%v"`, err, verror.ErrBadState) |
| } |
| } |
| } |
| |
| func TestStreamReadTerminatedByServer(t *testing.T) { |
| // TODO(suharshs): Fix conn/readq to block for outstanding reads to ensure |
| // that io.EOF is returned when q.close is called before the context is cancelled. |
| t.Skip(`There is a race in flow.close that causes this test to flake.`) |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| cctx := withPrincipal(t, ctx, "client") |
| sctx := withPrincipal(t, ctx, "server") |
| |
| s := &streamRecvInGoroutineServer{c: make(chan error, 1)} |
| _, _, err := v23.WithNewDispatchingServer(sctx, "mountpoint/server", testServerDisp{s}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| call, err := v23.GetClient(cctx).StartCall(cctx, "mountpoint/server/suffix", "RecvInGoroutine", []interface{}{}) |
| if err != nil { |
| t.Fatalf("StartCall failed: %v", err) |
| } |
| |
| c := make(chan error, 1) |
| go func() { |
| for i := 0; true; i++ { |
| if err := call.Send(i); err != nil { |
| c <- err |
| return |
| } |
| } |
| }() |
| |
| // The goroutine at the server executing "Recv" should have terminated |
| // with EOF. |
| if err := <-s.c; err != io.EOF { |
| t.Errorf("Got %v at server, want io.EOF", err) |
| } |
| // The client Send should have failed since the RPC has been |
| // terminated. |
| if err := <-c; err == nil { |
| t.Errorf("Client Send should fail as the server should have closed the flow") |
| } |
| } |
| |
| func TestPreferredAddress(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| |
| sctx := withPrincipal(t, ctx, "server") |
| pa := netstate.AddressChooserFunc(func(string, []net.Addr) ([]net.Addr, error) { |
| return []net.Addr{netstate.NewNetAddr("tcp", "1.1.1.1")}, nil |
| }) |
| sctx = v23.WithListenSpec(sctx, rpc.ListenSpec{ |
| Addrs: rpc.ListenAddrs{{"tcp", ":0"}}, |
| AddressChooser: pa, |
| }) |
| _, server, err := v23.WithNewServer(sctx, "", &testServer{}, nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| iep := server.Status().Endpoints[0] |
| host, _, err := net.SplitHostPort(iep.Address) |
| if err != nil { |
| t.Errorf("unexpected error: %s", err) |
| } |
| if got, want := host, "1.1.1.1"; got != want { |
| t.Errorf("got %q, want %q", got, want) |
| } |
| } |
| |
| func TestPreferredAddressErrors(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| |
| sctx := withPrincipal(t, ctx, "server") |
| paerr := netstate.AddressChooserFunc(func(_ string, a []net.Addr) ([]net.Addr, error) { |
| return nil, fmt.Errorf("oops") |
| }) |
| sctx = v23.WithListenSpec(sctx, rpc.ListenSpec{ |
| Addrs: rpc.ListenAddrs{{"tcp", ":0"}}, |
| AddressChooser: paerr, |
| }) |
| _, server, err := v23.WithNewServer(sctx, "", &testServer{}, nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| status := server.Status() |
| if got, want := len(status.Endpoints), 1; got != want { |
| t.Errorf("got %d, want %d", got, want) |
| } |
| if got, want := len(status.ListenErrors), 1; got != want { |
| t.Errorf("got %d, want %d", got, want) |
| } |
| addr := v23.GetListenSpec(sctx).Addrs[0] |
| if got, want := status.ListenErrors[addr].Error(), "oops"; !strings.Contains(got, want) { |
| t.Errorf("got %d, want %d", got, want) |
| } |
| } |
| |
| func TestGranter(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| cctx := withPrincipal(t, ctx, "client") |
| sctx := withPrincipal(t, ctx, "server") |
| _, _, err := v23.WithNewDispatchingServer(sctx, "mountpoint/server", &testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| tests := []struct { |
| granter rpc.Granter |
| startErrID, finishErrID verror.IDAction |
| blessing, starterr, finisherr string |
| }{ |
| {blessing: ""}, |
| {granter: granter{}, blessing: "test-blessing:client:blessed"}, |
| { |
| granter: granter{b: bless(t, cctx, sctx, "blessed")}, |
| blessing: "test-blessing:client:blessed", |
| }, |
| { |
| granter: granter{err: errors.New("hell no")}, |
| startErrID: verror.ErrNotTrusted, |
| starterr: "hell no", |
| }, |
| { |
| granter: granter{b: defaultBlessings(cctx)}, |
| finishErrID: verror.ErrNoAccess, |
| finisherr: "blessing granted not bound to this server", |
| }, |
| } |
| for i, test := range tests { |
| call, err := v23.GetClient(cctx).StartCall(cctx, |
| "mountpoint/server/suffix", |
| "EchoGrantedBlessings", |
| []interface{}{"argument"}, |
| test.granter) |
| if !matchesErrorPattern(err, test.startErrID, test.starterr) { |
| t.Errorf("%d: %+v: StartCall returned error %v", i, test, err) |
| } |
| if err != nil { |
| continue |
| } |
| var result, blessing string |
| if err = call.Finish(&result, &blessing); !matchesErrorPattern(err, test.finishErrID, 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 TestRPCClientAuthorization(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| |
| type v []interface{} |
| var ( |
| cctx = withPrincipal(t, ctx, "client") |
| sctx = withPrincipal(t, ctx, "server") |
| now = time.Now() |
| serverName = "mountpoint/server" |
| dischargeServerName = "mountpoint/dischargeserver" |
| |
| // Caveats on blessings to the client: First-party caveats |
| cavOnlyEcho = mkCaveat(security.NewMethodCaveat("Echo")) |
| cavExpired = mkCaveat(security.NewExpiryCaveat(now.Add(-1 * time.Second))) |
| // Caveats on blessings to the client: Third-party caveats |
| cavTPValid = mkThirdPartyCaveat( |
| v23.GetPrincipal(ctx).PublicKey(), |
| dischargeServerName, |
| mkCaveat(security.NewExpiryCaveat(now.Add(24*time.Hour)))) |
| cavTPExpired = mkThirdPartyCaveat(v23.GetPrincipal(ctx).PublicKey(), |
| dischargeServerName, |
| mkCaveat(security.NewExpiryCaveat(now.Add(-1*time.Second)))) |
| |
| // Client blessings that will be tested. |
| bServerClientOnlyEcho = bless(t, sctx, cctx, "onlyecho", cavOnlyEcho) |
| bServerClientExpired = bless(t, sctx, cctx, "expired", cavExpired) |
| bServerClientTPValid = bless(t, sctx, cctx, "dischargeable_third_party_caveat", cavTPValid) |
| bServerClientTPExpired = bless(t, sctx, cctx, "expired_third_party_caveat", cavTPExpired) |
| bClient, _ = v23.GetPrincipal(cctx).BlessingStore().Default() |
| bRandom, _ = v23.GetPrincipal(cctx).BlessSelf("random") |
| |
| tests = []struct { |
| blessings security.Blessings // Blessings used by the client |
| name string // object name on which the method is invoked |
| method string |
| args v |
| results v |
| authorized bool // Whether or not the RPC should be authorized by the server. |
| }{ |
| // There are three different authorization policies |
| // (security.Authorizer implementations) used by the server, |
| // depending on the suffix (see testServerDisp.Lookup): |
| // |
| // - nilAuth suffix: the default authorization policy (only |
| // delegates of or delegators of the server can call RPCs) |
| // |
| // - aclAuth suffix: the AccessList only allows blessings |
| // matching the patterns "server" or "client" |
| // |
| // - other suffixes: testServerAuthorizer allows any principal |
| // to call any method except "Unauthorized" |
| // |
| // Expired blessings should fail nilAuth and aclAuth (which care |
| // about names), but should succeed on other suffixes (which |
| // allow all blessings), unless calling the Unauthorized method. |
| {bServerClientExpired, "mountpoint/server/nilAuth", "Echo", v{"foo"}, v{""}, false}, |
| {bServerClientExpired, "mountpoint/server/aclAuth", "Echo", v{"foo"}, v{""}, false}, |
| {bServerClientExpired, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, true}, |
| {bServerClientExpired, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, false}, |
| |
| // Same for blessings that should fail to obtain a discharge for |
| // the third party caveat. |
| {bServerClientTPExpired, "mountpoint/server/nilAuth", "Echo", v{"foo"}, v{""}, false}, |
| {bServerClientTPExpired, "mountpoint/server/aclAuth", "Echo", v{"foo"}, v{""}, false}, |
| {bServerClientTPExpired, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, true}, |
| {bServerClientTPExpired, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, false}, |
| |
| // The "server:client" blessing (with MethodCaveat("Echo")) |
| // should satisfy all authorization policies when "Echo" is |
| // called. |
| {bServerClientOnlyEcho, "mountpoint/server/nilAuth", "Echo", v{"foo"}, v{""}, true}, |
| {bServerClientOnlyEcho, "mountpoint/server/aclAuth", "Echo", v{"foo"}, v{""}, true}, |
| {bServerClientOnlyEcho, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, true}, |
| |
| // The "server:client" blessing (with MethodCaveat("Echo")) |
| // should satisfy no authorization policy when any other method |
| // is invoked, except for the testServerAuthorizer policy (which |
| // will not recognize the blessing "server:onlyecho", but it |
| // would authorize anyone anyway). |
| {bServerClientOnlyEcho, "mountpoint/server/nilAuth", "Closure", nil, nil, false}, |
| {bServerClientOnlyEcho, "mountpoint/server/aclAuth", "Closure", nil, nil, false}, |
| {bServerClientOnlyEcho, "mountpoint/server/suffix", "Closure", nil, nil, true}, |
| |
| // The "client" blessing doesn't satisfy the default |
| // authorization policy, but does satisfy the AccessList and the |
| // testServerAuthorizer policy. |
| {bClient, "mountpoint/server/nilAuth", "Echo", v{"foo"}, v{""}, false}, |
| {bClient, "mountpoint/server/aclAuth", "Echo", v{"foo"}, v{""}, true}, |
| {bClient, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, true}, |
| {bClient, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, false}, |
| |
| // The "random" blessing does not satisfy either the default |
| // policy or the AccessList, but does satisfy |
| // testServerAuthorizer. |
| {bRandom, "mountpoint/server/nilAuth", "Echo", v{"foo"}, v{""}, false}, |
| {bRandom, "mountpoint/server/aclAuth", "Echo", v{"foo"}, v{""}, false}, |
| {bRandom, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, true}, |
| {bRandom, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, false}, |
| |
| // The "server:dischargeable_third_party_caveat" blessing satisfies all policies. |
| // (the discharges should be fetched). |
| {bServerClientTPValid, "mountpoint/server/nilAuth", "Echo", v{"foo"}, v{""}, true}, |
| {bServerClientTPValid, "mountpoint/server/aclAuth", "Echo", v{"foo"}, v{""}, true}, |
| {bServerClientTPValid, "mountpoint/server/suffix", "Echo", v{"foo"}, v{""}, true}, |
| {bServerClientTPValid, "mountpoint/server/suffix", "Unauthorized", nil, v{""}, false}, |
| } |
| ) |
| // Start the discharge server. |
| _, _, err := v23.WithNewServer(ctx, dischargeServerName, &dischargeServer{}, security.AllowEveryone()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Start the main server. |
| _, _, err = v23.WithNewDispatchingServer(sctx, serverName, testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // The server should recognize the client principal as an authority |
| // on "random" blessings. |
| security.AddToRoots(v23.GetPrincipal(sctx), bRandom) |
| |
| // Set a blessing on the client's blessing store to be presented to |
| // the discharge server. |
| v23.GetPrincipal(cctx).BlessingStore().Set(defaultBlessings(cctx), "test-blessing:$") |
| |
| // testutil.NewPrincipal sets up a principal that shares blessings |
| // with all servers, undo that. |
| v23.GetPrincipal(cctx).BlessingStore().Set( |
| security.Blessings{}, security.AllPrincipals) |
| |
| for i, test := range tests { |
| name := fmt.Sprintf("#%d: %q.%s(%v) by %v", i, test.name, test.method, test.args, test.blessings) |
| client := v23.GetClient(cctx) |
| |
| v23.GetPrincipal(cctx).BlessingStore().Set(test.blessings, "test-blessing:server") |
| err = client.Call(cctx, test.name, test.method, test.args, makeResultPtrs(test.results)) |
| if err != nil && test.authorized { |
| t.Errorf(`%s client.Call got error: "%v", wanted the RPC to succeed`, name, err) |
| } else if err == nil && !test.authorized { |
| t.Errorf("%s call.Finish succeeded, expected authorization failure", name) |
| } else if !test.authorized && verror.ErrorID(err) != verror.ErrNoAccess.ID { |
| t.Errorf("%s. call.Finish returned error %v(%v), wanted %v", name, verror.ErrorID(verror.Convert(verror.ErrNoAccess, nil, err)), err, verror.ErrNoAccess) |
| } |
| } |
| } |
| |
| func TestRPCServerAuthorization(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| |
| const ( |
| publicKeyErr = "not the authorized public key" |
| missingDischargeErr = "missing discharge" |
| expiryErr = "is after expiry" |
| allowedErr = "do not match any allowed server patterns" |
| ) |
| type O []rpc.CallOpt // shorthand |
| var ( |
| sctx = withPrincipal(t, ctx, "server") |
| now = time.Now() |
| noErrID verror.IDAction |
| |
| // Third-party caveats on blessings presented by server. |
| cavTPValid = mkThirdPartyCaveat( |
| v23.GetPrincipal(ctx).PublicKey(), |
| "mountpoint/dischargeserver", |
| mkCaveat(security.NewExpiryCaveat(now.Add(24*time.Hour)))) |
| |
| cavTPExpired = mkThirdPartyCaveat( |
| v23.GetPrincipal(ctx).PublicKey(), |
| "mountpoint/dischargeserver", |
| mkCaveat(security.NewExpiryCaveat(now.Add(-1*time.Second)))) |
| |
| // Server blessings. |
| bServer = bless(t, ctx, sctx, "server") |
| bServerExpired = bless(t, ctx, sctx, "expiredserver", mkCaveat(security.NewExpiryCaveat(time.Now().Add(-1*time.Second)))) |
| bServerTPValid = bless(t, ctx, sctx, "serverWithTPCaveats", cavTPValid) |
| bServerTPExpired = bless(t, ctx, sctx, "serverWithExpiredTPCaveats", cavTPExpired) |
| bOther = bless(t, ctx, sctx, "other") |
| bTwoBlessings, _ = security.UnionOfBlessings(bServer, bOther) |
| |
| ACL = func(patterns ...string) security.Authorizer { |
| var acl access.AccessList |
| for _, p := range patterns { |
| acl.In = append(acl.In, security.BlessingPattern(p)) |
| } |
| return acl |
| } |
| |
| tests = []struct { |
| server security.Blessings // blessings presented by the server to the client. |
| name string // name provided by the client to StartCall |
| opts O // options provided to StartCall. |
| errID verror.IDAction |
| err string |
| }{ |
| // Client accepts talking to the server only if the |
| // server presents valid blessings (and discharges) |
| // consistent with the ones published in the endpoint. |
| {bServer, "mountpoint/server", nil, noErrID, ""}, |
| {bServerTPValid, "mountpoint/server", nil, noErrID, ""}, |
| |
| // Client will not talk to a server that presents |
| // expired blessings or is missing discharges. |
| {bServerExpired, "mountpoint/server", nil, verror.ErrNotTrusted, expiryErr}, |
| {bServerTPExpired, "mountpoint/server", nil, verror.ErrNotTrusted, missingDischargeErr}, |
| |
| // Test the ServerAuthorizer option. |
| {bOther, "mountpoint/server", O{options.ServerAuthorizer{security.PublicKeyAuthorizer(bOther.PublicKey())}}, noErrID, ""}, |
| {bOther, "mountpoint/server", O{options.ServerAuthorizer{security.PublicKeyAuthorizer(testutil.NewPrincipal("irrelevant").PublicKey())}}, verror.ErrNotTrusted, publicKeyErr}, |
| |
| // Test the "paranoid" names, where the pattern is provided in the name. |
| {bServer, "__(test-blessing:server)/mountpoint/server", nil, noErrID, ""}, |
| {bServer, "__(test-blessing:other)/mountpoint/server", nil, verror.ErrNotTrusted, allowedErr}, |
| {bTwoBlessings, "__(test-blessing:server)/mountpoint/server", O{options.ServerAuthorizer{ACL("test-blessing:other")}}, noErrID, ""}, |
| } |
| ) |
| // Start the discharge server. |
| _, _, err := v23.WithNewServer(ctx, "mountpoint/dischargeserver", &dischargeServer{}, security.AllowEveryone()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| for i, test := range tests { |
| scctx, cancel := context.WithCancel(sctx) |
| name := fmt.Sprintf("(#%d: Name:%q, Server:%q, opts:%v)", |
| i, test.name, test.server, test.opts) |
| if err := vsecurity.SetDefaultBlessings(v23.GetPrincipal(sctx), test.server); err != nil { |
| t.Fatal(err) |
| } |
| _, s, err := v23.WithNewDispatchingServer(scctx, "mountpoint/server", &testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err, defaultBlessings(scctx)) |
| } |
| call, err := v23.GetClient(ctx).StartCall(ctx, test.name, "Method", nil, test.opts...) |
| if !matchesErrorPattern(err, test.errID, test.err) { |
| t.Errorf(`%s: client.StartCall: got error "%v", want to match "%v"`, |
| name, err, test.err) |
| } else if call != nil { |
| blessings, proof := call.RemoteBlessings() |
| if proof.IsZero() { |
| t.Errorf("%s: Returned zero value for remote blessings", name) |
| } |
| // Currently all tests are configured so that the only |
| // blessings presented by the server that are |
| // recognized by the client match the pattern |
| // "test-blessing" |
| if len(blessings) < 1 || !security.BlessingPattern("test-blessing").MatchedBy(blessings...) { |
| t.Errorf("%s: Client sees server as %v, expected a single blessing matching test-blessing", name, blessings) |
| } |
| } |
| cancel() |
| <-s.Closed() |
| } |
| } |
| |
| func TestServerManInTheMiddleAttack(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| // Test scenario: A server mounts itself, but then some other service |
| // somehow "takes over" the network endpoint (a naughty router |
| // perhaps), thus trying to steal traffic. |
| var ( |
| cctx = withPrincipal(t, ctx, "client") |
| actx = withPrincipal(t, ctx, "attacker") |
| ) |
| name := "mountpoint/server" |
| _, aserver, err := v23.WithNewDispatchingServer(actx, "", testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // The legitimate server would have mounted the same endpoint on the |
| // namespace, but with different blessings. |
| ep := aserver.Status().Endpoints[0].WithBlessingNames([]string{"test-blessings/server"}) |
| if err := v23.GetNamespace(actx).Mount(ctx, name, ep.Name(), time.Hour); err != nil { |
| t.Fatal(err) |
| } |
| |
| // The RPC call should fail because the blessings presented by the |
| // (attacker's) server are not consistent with the ones registered in |
| // the mounttable trusted by the client. |
| if _, err := v23.GetClient(cctx).StartCall(cctx, "mountpoint/server", "Closure", nil); verror.ErrorID(err) != verror.ErrNotTrusted.ID { |
| t.Errorf("Got error %v (errorid=%v), want errorid=%v", err, verror.ErrorID(err), verror.ErrNotTrusted.ID) |
| } |
| // But the RPC should succeed if the client explicitly |
| // decided to skip server authorization. |
| if err := v23.GetClient(cctx).Call(cctx, "mountpoint/server", "Closure", nil, nil, options.ServerAuthorizer{security.AllowEveryone()}); err != nil { |
| t.Errorf("Unexpected error(%v) when skipping server authorization", err) |
| } |
| } |
| |
| func TestDischargeImpetusAndContextPropagation(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| sctx := withPrincipal(t, ctx, "server") |
| cctx := withPrincipal(t, ctx, "client") |
| |
| // Setup the client so that it shares a blessing with a third-party caveat with the server. |
| setClientBlessings := func(req security.ThirdPartyRequirements) { |
| cav, err := security.NewPublicKeyCaveat( |
| v23.GetPrincipal(ctx).PublicKey(), |
| "mountpoint/discharger", |
| req, |
| security.UnconstrainedUse()) |
| if err != nil { |
| t.Fatalf("Failed to create ThirdPartyCaveat(%+v): %v", req, err) |
| } |
| b, err := v23.GetPrincipal(cctx).BlessSelf("client_for_server", cav) |
| if err != nil { |
| t.Fatalf("BlessSelf failed: %v", err) |
| } |
| v23.GetPrincipal(cctx).BlessingStore().Set(b, "test-blessing:server") |
| } |
| |
| // Setup the discharge server. |
| var tester dischargeTestServer |
| _, _, err := v23.WithNewServer(ctx, "mountpoint/discharger", &tester, &testServerAuthorizer{}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Setup the application server. |
| sctx = v23.WithListenSpec(sctx, rpc.ListenSpec{ |
| Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}, |
| }) |
| object := "mountpoint/object" |
| _, _, err = v23.WithNewServer(sctx, object, &testServer{}, &testServerAuthorizer{}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| tests := []struct { |
| Requirements security.ThirdPartyRequirements |
| Impetus security.DischargeImpetus |
| }{ |
| { // No requirements, no impetus |
| Requirements: security.ThirdPartyRequirements{}, |
| Impetus: security.DischargeImpetus{}, |
| }, |
| { // Require everything |
| Requirements: security.ThirdPartyRequirements{ReportServer: true, ReportMethod: true, ReportArguments: true}, |
| Impetus: security.DischargeImpetus{Server: []security.BlessingPattern{"test-blessing:server"}, Method: "Method", Arguments: []*vom.RawBytes{vom.RawBytesOf("argument")}}, |
| }, |
| { // Require only the method name |
| Requirements: security.ThirdPartyRequirements{ReportMethod: true}, |
| Impetus: security.DischargeImpetus{Method: "Method"}, |
| }, |
| } |
| |
| for _, test := range tests { |
| setClientBlessings(test.Requirements) |
| tid := vtrace.GetSpan(cctx).Trace() |
| // StartCall should fetch the discharge, do not worry about finishing the RPC - do not care about that for this test. |
| if _, err := v23.GetClient(cctx).StartCall(cctx, object, "Method", []interface{}{"argument"}); err != nil { |
| t.Errorf("StartCall(%+v) failed: %v", test.Requirements, err) |
| continue |
| } |
| impetus, traceid := tester.Release() |
| // There should have been exactly 1 attempt to fetch discharges when making |
| // the RPC to the remote object. |
| if len(impetus) != 1 || len(traceid) != 1 { |
| t.Errorf("Test %+v: Got (%d, %d) (#impetus, #traceid), wanted exactly one", test.Requirements, len(impetus), len(traceid)) |
| continue |
| } |
| // VC creation does not have any "impetus", it is established without |
| // knowledge of the context of the RPC. So ignore that. |
| // |
| // TODO(ashankar): Should the impetus of the RPC that initiated the |
| // VIF/VC creation be propagated? |
| if got, want := vdl.ValueOf(impetus[len(impetus)-1]), vdl.ValueOf(test.Impetus); !vdl.EqualValue(got, want) { |
| t.Errorf("Test %+v: Got impetus %#v, want %#v", test.Requirements, got, want) |
| } |
| // But the context used for all of this should be the same |
| // (thereby allowing debug traces to link VIF/VC creation with |
| // the RPC that initiated them). |
| for idx, got := range traceid { |
| if !reflect.DeepEqual(got, tid) { |
| t.Errorf("Test %+v: %d - Got trace id %q, want %q", test.Requirements, idx, hex.EncodeToString(got[:]), hex.EncodeToString(tid[:])) |
| } |
| } |
| } |
| } |
| |
| func TestRPCClientBlessingsPublicKey(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| |
| var ( |
| sctx = withPrincipal(t, ctx, "server") |
| bserver = defaultBlessings(sctx) |
| cctx = withPrincipal(t, ctx, "client") |
| bclient = defaultBlessings(cctx) |
| ) |
| cctx, err := v23.WithPrincipal(cctx, |
| vsecurity.MustForkPrincipal( |
| v23.GetPrincipal(cctx), |
| &singleBlessingStore{pk: v23.GetPrincipal(cctx).PublicKey()}, |
| vsecurity.ImmutableBlessingRoots(v23.GetPrincipal(ctx).Roots()))) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| bvictim := defaultBlessings(withPrincipal(t, ctx, "victim")) |
| |
| _, s, err := v23.WithNewDispatchingServer(sctx, "", testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| object := naming.Join(s.Status().Endpoints[0].Name(), "suffix") |
| |
| tests := []struct { |
| blessings security.Blessings |
| errID verror.IDAction |
| err bool |
| }{ |
| {blessings: bclient}, |
| // server disallows clients from authenticating with blessings not bound to |
| // the client principal's public key |
| {blessings: bvictim, errID: verror.ErrNoAccess, err: true}, |
| {blessings: bserver, errID: verror.ErrNoAccess, err: true}, |
| } |
| for i, test := range tests { |
| name := fmt.Sprintf("%d: Client RPCing with blessings %v", i, test.blessings) |
| v23.GetPrincipal(cctx).BlessingStore().Set(test.blessings, "test-blessings") |
| if err := v23.GetClient(cctx).Call(cctx, object, "Closure", nil, nil); test.err && err == nil { |
| t.Errorf("%v: client.Call returned error %v", name, err) |
| continue |
| } |
| } |
| } |
| |
| func TestServerLocalBlessings(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| tpCav := mkThirdPartyCaveat( |
| v23.GetPrincipal(ctx).PublicKey(), |
| "mountpoint/dischargeserver", |
| mkCaveat(security.NewExpiryCaveat(time.Now().Add(time.Hour)))) |
| sctx := withPrincipal(t, ctx, "server", tpCav) |
| cctx := withPrincipal(t, ctx, "client") |
| |
| _, _, err := v23.WithNewServer(ctx, "mountpoint/dischargeserver", &dischargeServer{}, security.AllowEveryone()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| _, _, err = v23.WithNewDispatchingServer(sctx, "mountpoint/server", testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| var gotServer, gotClient string |
| if err := v23.GetClient(cctx).Call(cctx, "mountpoint/server/suffix", "EchoBlessings", nil, []interface{}{&gotServer, &gotClient}); err != nil { |
| t.Fatalf("Finish failed: %v", err) |
| } |
| if wantServer, wantClient := "[test-blessing:server]", "[test-blessing:client]"; gotServer != wantServer || gotClient != wantClient { |
| t.Fatalf("EchoBlessings: got %v, %v want %v, %v", gotServer, gotClient, wantServer, wantClient) |
| } |
| } |
| |
| func TestDischargesToMounttable(t *testing.T) { |
| // This test duplicates the underlying problem in |
| // https://github.com/vanadium/issues/issues/1049 |
| // |
| // The mounttable is used once to resolve the name of the discharger |
| // and then again to mount. The first time discharges aren't sent (as |
| // they aren't available). The second time they must be sent. |
| ctx, shutdown := test.V23InitWithMounttable() |
| name := "mountpoint/name" |
| defer shutdown() |
| |
| _, _, err := v23.WithNewServer(ctx, "mountpoint/dischargeserver", &dischargeServer{}, security.AllowEveryone()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| // Set an ACL on the mounttable so that only test-blessing:client authorizes a mount. |
| perms := make(access.Permissions) |
| perms.Add("test-blessing:client", string(access.Write)) |
| if err := v23.GetNamespace(ctx).SetPermissions(ctx, name, perms, ""); err != nil { |
| t.Fatal(err) |
| } |
| |
| tpCav := mkThirdPartyCaveat( |
| v23.GetPrincipal(ctx).PublicKey(), |
| "mountpoint/dischargeserver", |
| mkCaveat(security.NewExpiryCaveat(time.Now().Add(time.Hour)))) |
| cctx := withPrincipal(t, ctx, "client", tpCav) |
| if err := v23.GetNamespace(cctx).Mount(cctx, name, "localhost:14141", time.Second); err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestDischargePurgeFromCache(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| |
| sctx := withPrincipal(t, ctx, "server") |
| // Client is blessed with a third-party caveat. The discharger |
| // service issues discharges with a fakeTimeCaveat. This blessing |
| // is presented to "server". |
| cctx := withPrincipal(t, ctx, "client", |
| mkThirdPartyCaveat( |
| v23.GetPrincipal(ctx).PublicKey(), |
| "mountpoint/dischargeserver", |
| security.UnconstrainedUse())) |
| |
| _, _, err := v23.WithNewServer(ctx, "mountpoint/dischargeserver", &dischargeServer{}, security.AllowEveryone()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| _, _, err = v23.WithNewDispatchingServer(sctx, "mountpoint/server", testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| call := func() error { |
| var got string |
| if err := v23.GetClient(cctx).Call(cctx, "mountpoint/server/aclAuth", "Echo", []interface{}{"batman"}, []interface{}{&got}); err != nil { |
| return err |
| } |
| if want := `method:"Echo",suffix:"aclAuth",arg:"batman"`; got != want { |
| return verror.Convert(verror.ErrBadArg, nil, 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, want := call(), "not authorized"; !matchesErrorPattern(err, verror.ErrNoAccess, want) { |
| t.Errorf("Got error [%v] wanted to match pattern %q", err, want) |
| } |
| // But retrying will succeed since the discharge should be purged |
| // from cache and refreshed |
| if err := call(); err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestReplayAttack(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| |
| sctx := withPrincipal(t, ctx, "server") |
| cctx := withPrincipal(t, ctx, "client") |
| |
| _, s, err := v23.WithNewDispatchingServer(sctx, "", testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| ep := s.Status().Endpoints[0] |
| addr := ep.Addr() |
| |
| // Dial the server. |
| tcp := tcputil.TCP{} |
| conn, err := tcp.Dial(cctx, addr.Network(), addr.String(), time.Minute) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer conn.Close() |
| |
| // Read the setup message from the server. |
| b, err := conn.ReadMsg() |
| if err != nil { |
| t.Fatal(err) |
| } |
| m, err := message.Read(cctx, b) |
| if err != nil { |
| t.Fatal(err) |
| } |
| rSetup, ok := m.(*message.Setup) |
| if !ok { |
| t.Fatalf("got %#v, want *message.Setup", m) |
| } |
| rpk := rSetup.PeerNaClPublicKey |
| |
| // Send our setup message to the server. |
| pk, sk, err := box.GenerateKey(rand.Reader) |
| if err != nil { |
| t.Fatal(err) |
| } |
| lSetup := &message.Setup{ |
| Versions: rSetup.Versions, |
| PeerLocalEndpoint: rSetup.PeerLocalEndpoint, |
| PeerNaClPublicKey: pk, |
| } |
| b, err = message.Append(ctx, lSetup, nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if _, err = conn.WriteMsg(b); err != nil { |
| t.Fatal(err) |
| } |
| |
| // Setup encryption to the server. |
| cipher := crypto.NewControlCipherRPC11( |
| (*crypto.BoxKey)(pk), |
| (*crypto.BoxKey)(sk), |
| (*crypto.BoxKey)(rpk)) |
| |
| // Read the auth message from the server. |
| for auth := false; !auth; { |
| b, err = conn.ReadMsg() |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !cipher.Open(b) { |
| t.Fatal("failed to decrypt message") |
| } |
| m, err = message.Read(ctx, b[:len(b)-cipher.MACSize()]) |
| if err != nil { |
| t.Fatal(err) |
| } |
| switch m.(type) { |
| case *message.Auth: |
| auth = true |
| case *message.Data: |
| default: |
| continue |
| } |
| if b, err = message.Append(ctx, m, nil); err != nil { |
| t.Fatal(err) |
| } |
| tmp := make([]byte, len(b)+cipher.MACSize()) |
| copy(tmp, b) |
| b = tmp |
| if err = cipher.Seal(b); err != nil { |
| t.Fatal(err) |
| } |
| if _, err = conn.WriteMsg(b); err != nil { |
| t.Fatal(err) |
| } |
| |
| } |
| |
| // The server should send a tearDown message complaining about the channel binding. |
| b, err = conn.ReadMsg() |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !cipher.Open(b) { |
| t.Fatal("failed to decrypt message") |
| } |
| m, err = message.Read(ctx, b[:len(b)-cipher.MACSize()]) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if _, ok = m.(*message.TearDown); !ok { |
| t.Fatalf("got %#v, want *message.TearDown", m) |
| } |
| } |
| |
| func TestPrivateServer(t *testing.T) { |
| ctx, shutdown := test.V23Init() |
| defer shutdown() |
| |
| // The following is so that we have a known default blessing name |
| // for the root context. |
| ctx, err := v23.WithPrincipal(ctx, testutil.NewPrincipal("root")) |
| if err != nil { |
| t.Fatal(err) |
| } |
| master, err := ibe.SetupBB1() |
| if err != nil { |
| t.Fatal(err) |
| } |
| root := bcrypter.NewRoot("root", master) |
| |
| // Derive the client and server contexts |
| cctx := withPrincipal(t, ctx, "client") |
| cctx = bcrypter.WithCrypter(cctx, bcrypter.NewCrypter()) |
| |
| sctx := withPrincipal(t, ctx, "server") |
| sctx = bcrypter.WithCrypter(sctx, bcrypter.NewCrypter()) |
| |
| // Add the root params to the server's crypter so that the |
| // server can use IBE to keep its blessings private. |
| if err := bcrypter.GetCrypter(sctx).AddParams(sctx, root.Params()); err != nil { |
| t.Fatal(err) |
| } |
| |
| // Test that creating a server with an empty set of server peers |
| // fails |
| _, _, err = v23.WithNewDispatchingServer(sctx, "", &testServerDisp{&testServer{}}, options.ServerPeers([]security.BlessingPattern{})) |
| if err == nil { |
| t.Fatal("WithNewDispatchingServer unexpectedly passed with empty set of server peers") |
| } |
| |
| // Test that creating a server with a non-empty set of server peers and |
| // a non-empty name fails. |
| _, _, err = v23.WithNewDispatchingServer(sctx, "foo", &testServerDisp{&testServer{}}, options.ServerPeers([]security.BlessingPattern{})) |
| if err == nil { |
| t.Fatal("WithNewDispatchingServer unexpectedly passed with a non-empty set of server peers and a non-empty name") |
| } |
| |
| // Create a server that only reveals its blesings to peers with blessings |
| // matching the pattern "root:client". |
| // |
| // Since a mounttable server always learns a server's blessings, this also |
| // restricts the set of mounttable servers that the server can talk to. In |
| // particular the mounttable server must have a blessing matching the pattern |
| // "root:client". |
| // |
| // Having said that, the common case for private mutual authentication would |
| // be when there are no mounttables involved and the server's endpoint is |
| // discovered using a peer-to-peer discovery mechanism (e.g., mdns). |
| _, server, err := v23.WithNewDispatchingServer(sctx, "", &testServerDisp{&testServer{}}, options.ServerPeers([]security.BlessingPattern{"root:client"})) |
| if err != nil { |
| t.Fatal(err) |
| } |
| serverEPName := server.Status().Endpoints[0].Name() |
| client := v23.GetClient(cctx) |
| |
| // Test that an RPC to the server fails as the client has no blessing private keys. |
| call, err := client.StartCall(cctx, serverEPName, "Closure", nil) |
| if err == nil { |
| t.Error("RPC by client with no blessing private key unexpectedly succeeded") |
| } |
| |
| // Add a private key for a blessing that does not match the server's policy |
| // (pattern: "root:client") and test that the RPC still fails. |
| if err := bcrypter.GetCrypter(cctx).AddKey(cctx, extractKey(t, ctx, root, "root:otherclient")); err != nil { |
| t.Fatal(err) |
| } |
| call, err = client.StartCall(cctx, serverEPName, "Closure", nil) |
| if err == nil { |
| t.Error("RPC by client with no matching blessing private key unexpectedly succeeded") |
| } |
| |
| // Add a private key for a blessing matching the server's policy and test that |
| // the RPC succceeds and that the client learns the server's blessing ("root:server"). |
| if err := bcrypter.GetCrypter(cctx).AddKey(cctx, extractKey(t, ctx, root, "root:client")); err != nil { |
| t.Fatal(err) |
| } |
| call, err = client.StartCall(cctx, serverEPName, "Closure", nil, options.ServerAuthorizer{access.AccessList{In: []security.BlessingPattern{"root:server:$"}}}) |
| if err != nil { |
| t.Error(verror.DebugString(err)) |
| } else { |
| results := makeResultPtrs(nil) |
| if err := call.Finish(results...); err != nil { |
| t.Error("RPC by client with a matching blessing private key failed: %v", err) |
| } |
| } |
| } |
| |
| type publicKeyAuth struct { |
| pkey security.PublicKey |
| } |
| |
| func (a *publicKeyAuth) Authorize(ctx *context.T, call security.Call) error { |
| pkey := call.RemoteBlessings().PublicKey() |
| if !reflect.DeepEqual(a.pkey, pkey) { |
| return fmt.Errorf("public key mismatch: %v != %v", a.pkey, pkey) |
| } |
| return nil |
| } |
| |
| func TestNamelessClientBlessings(t *testing.T) { |
| ctx, shutdown := test.V23Init() |
| defer shutdown() |
| |
| p, err := vsecurity.NewPrincipal() |
| if err != nil { |
| t.Fatal(err) |
| } |
| clientCtx, err := v23.WithPrincipal(ctx, p) |
| if err != nil { |
| t.Fatal(err) |
| } |
| clt := v23.GetClient(clientCtx) |
| |
| _, server, err := v23.WithNewServer(ctx, "", &testServer{}, &publicKeyAuth{p.PublicKey()}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| name := server.Status().Endpoints[0].Name() |
| |
| if err := clt.Call(clientCtx, name, "Closure", nil, nil, options.ServerAuthorizer{security.AllowEveryone()}); err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestNamelessServerBlessings(t *testing.T) { |
| ctx, shutdown := test.V23Init() |
| defer shutdown() |
| |
| client := v23.GetClient(ctx) |
| |
| p, err := vsecurity.NewPrincipal() |
| if err != nil { |
| t.Fatal(err) |
| } |
| sctx, err := v23.WithPrincipal(ctx, p) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| _, server, err := v23.WithNewDispatchingServer(sctx, "", &testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| name := server.Status().Endpoints[0].Name() |
| |
| if err := client.Call(ctx, name, "Closure", nil, nil, options.ServerAuthorizer{&publicKeyAuth{p.PublicKey()}}); err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestServerRefreshDischarges(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| |
| sctx := withPrincipal(t, ctx, "server", mkThirdPartyCaveat( |
| v23.GetPrincipal(ctx).PublicKey(), |
| "mountpoint/dischargeserver", |
| security.UnconstrainedUse())) |
| cctx := withPrincipal(t, ctx, "client") |
| |
| ed := &expiryDischarger{} |
| _, _, err := v23.WithNewServer(ctx, "mountpoint/dischargeserver", ed, security.AllowEveryone()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| sctx, cancel := context.WithCancel(sctx) |
| _, server, err := v23.WithNewDispatchingServer(sctx, "mountpoint/server", testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { <-server.Closed() }() |
| defer cancel() |
| |
| // Make a call to create a connection. We don't care if the call succeeds, |
| // we just want to make sure that we fetch discharges more than once. |
| var got string |
| cctx, cancel = context.WithCancel(cctx) |
| defer cancel() |
| v23.GetClient(cctx).Call(cctx, "mountpoint/server/aclAuth", "Echo", []interface{}{"batman"}, []interface{}{&got}, options.NoRetry{}) |
| for { |
| ed.mu.Lock() |
| count := ed.count |
| ed.mu.Unlock() |
| if count > 1 { |
| break |
| } |
| t.Logf("waiting for discharger to be called multiple times") |
| time.Sleep(50 * time.Millisecond) |
| } |
| } |
| |
| func TestClientRefreshDischarges(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| |
| sctx := withPrincipal(t, ctx, "server") |
| cctx := withPrincipal(t, ctx, "client", mkThirdPartyCaveat( |
| v23.GetPrincipal(ctx).PublicKey(), |
| "mountpoint/dischargeserver", |
| security.UnconstrainedUse())) |
| |
| d := &dischargeServer{} |
| _, _, err := v23.WithNewServer(ctx, "mountpoint/dischargeserver", d, security.AllowEveryone()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| sctx, cancel := context.WithCancel(sctx) |
| _, server, err := v23.WithNewDispatchingServer(sctx, "mountpoint/server", testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { <-server.Closed() }() |
| defer cancel() |
| |
| // Make a call to create a server connection. We don't care if the call succeeds, |
| // we just want to make sure that we fetch discharges only once when we are a client. |
| var got string |
| cctx, cancel = context.WithCancel(cctx) |
| defer cancel() |
| v23.GetClient(cctx).Call(cctx, "mountpoint/server/aclAuth", "Echo", []interface{}{"batman"}, []interface{}{&got}, options.NoRetry{}) |
| d.mu.Lock() |
| if d.count != 1 { |
| t.Errorf("discharger should have been called exactly once, got %v", d.count) |
| } |
| d.mu.Unlock() |
| } |
| |
| func TestBidirectionalRefreshDischarges(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| |
| sctx := withPrincipal(t, ctx, "server") |
| cctx := withPrincipal(t, ctx, "client", mkThirdPartyCaveat( |
| v23.GetPrincipal(ctx).PublicKey(), |
| "mountpoint/dischargeserver", |
| security.UnconstrainedUse())) |
| |
| ed := &expiryDischarger{} |
| _, _, err := v23.WithNewServer(ctx, "mountpoint/dischargeserver", ed, security.AllowEveryone()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| sctx, cancel := context.WithCancel(sctx) |
| defer cancel() |
| _, _, err = v23.WithNewDispatchingServer(sctx, "mountpoint/server", testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| cctx, cancel = context.WithCancel(cctx) |
| cctx, server, err := v23.WithNewDispatchingServer(cctx, "mountpoint/clientserver", testServerDisp{&testServer{}}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { <-server.Closed() }() |
| defer cancel() |
| |
| // Make a call to create a connection. We don't care if the call succeeds, |
| // we just want to make sure that we fetch discharges more than once. var got string |
| var got string |
| v23.GetClient(cctx).Call(cctx, "mountpoint/server/aclAuth", "Echo", []interface{}{"batman"}, []interface{}{&got}, options.NoRetry{}) |
| for { |
| ed.mu.Lock() |
| count := ed.count |
| ed.mu.Unlock() |
| if count > 1 { |
| break |
| } |
| t.Logf("waiting for discharger to be called multiple times") |
| time.Sleep(50 * time.Millisecond) |
| } |
| } |
| |
| type manInMiddleConn struct { |
| flow.Conn |
| ctx *context.T |
| } |
| |
| // manInMiddleConn changes the versions in any setup message sent over the wire. |
| func (c *manInMiddleConn) ReadMsg() ([]byte, error) { |
| b, err := c.Conn.ReadMsg() |
| if len(b) > 0 { |
| m, _ := message.Read(c.ctx, b) |
| switch msg := m.(type) { |
| case *message.Setup: |
| // The malicious man in the middle changes the max version to a bad version. |
| msg.Versions = version.RPCVersionRange{Min: version.RPCVersion10, Max: 100} |
| b, err = message.Append(c.ctx, msg, nil) |
| } |
| } |
| return b, err |
| } |
| |
| func TestSetupAttack(t *testing.T) { |
| ctx, shutdown := test.V23InitWithMounttable() |
| defer shutdown() |
| |
| ctx = debug.WithFilter(ctx, func(c flow.Conn) flow.Conn { |
| return &manInMiddleConn{c, ctx} |
| }) |
| |
| ctx = v23.WithListenSpec(ctx, rpc.ListenSpec{ |
| Addrs: rpc.ListenAddrs{{Protocol: "debug", Address: "tcp/127.0.0.1:0"}}, |
| }) |
| ctx, cancel := context.WithCancel(ctx) |
| _, server, err := v23.WithNewServer(ctx, "mountpoint/server", &testServer{}, nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer func() { <-server.Closed() }() |
| defer cancel() |
| // Connection establishment should fail during the RPC because the channel binding |
| // check should fail since the Setup message has been altered. |
| if err := v23.GetClient(ctx).Call(ctx, "mountpoint/server", "Closure", nil, nil, options.NoRetry{}); err == nil { |
| t.Errorf("expected error but got <nil>") |
| } |
| } |
| |
| func defaultBlessings(ctx *context.T) security.Blessings { |
| b, _ := v23.GetPrincipal(ctx).BlessingStore().Default() |
| return b |
| } |
| |
| type closeServer struct { |
| ch chan struct{} |
| } |
| |
| func (s *closeServer) Close(*context.T, rpc.ServerCall) ([20000]byte, error) { |
| defer close(s.ch) |
| return [20000]byte{}, nil |
| } |
| |
| func TestImmediateClose(t *testing.T) { |
| ctx, shutdown := test.V23Init() |
| defer shutdown() |
| |
| ch := make(chan struct{}) |
| sctx, cancel := context.WithCancel(ctx) |
| _, server, err := v23.WithNewServer(sctx, "", &closeServer{ch}, nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| name := server.Status().Endpoints[0].Name() |
| done := make(chan struct{}) |
| go func() { |
| var b [20000]byte |
| if err := v23.GetClient(ctx).Call(ctx, name, "Close", nil, []interface{}{&b}); err != nil { |
| t.Error(err) |
| } |
| close(done) |
| }() |
| |
| // Once the server responds to a request close the server. |
| <-ch |
| cancel() |
| <-server.Closed() |
| |
| // Wait for the client to finish up. |
| <-done |
| } |