blob: 1bee64edbb049ec23c9620d5b953370fbb898b08 [file] [log] [blame]
// 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"
"golang.org/x/crypto/nacl/box"
"io"
"net"
"reflect"
"strings"
"testing"
"time"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/flow/message"
"v.io/v23/i18n"
"v.io/v23/naming"
"v.io/v23/options"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/security/access"
"v.io/v23/vdl"
"v.io/v23/verror"
"v.io/v23/vtrace"
"v.io/x/lib/netstate"
"v.io/x/ref"
vsecurity "v.io/x/ref/lib/security"
"v.io/x/ref/runtime/internal/lib/tcputil"
inaming "v.io/x/ref/runtime/internal/naming"
"v.io/x/ref/runtime/internal/rpc/stream/crypto"
"v.io/x/ref/test"
"v.io/x/ref/test/testutil"
)
func TestAddRemoveName(t *testing.T) {
ctx, shutdown := test.V23Init()
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.V23Init()
defer shutdown()
call, err := v23.GetClient(ctx).StartCall(nil, "foo", "bar", []interface{}{}, options.SecurityNone)
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.V23Init()
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.V23Init()
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.V23Init()
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.V23Init()
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) {
if ref.RPCTransitionState() == ref.XServers {
// 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.V23Init()
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.V23Init()
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].(*inaming.Endpoint)
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.V23Init()
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 ref.RPCTransitionState() >= ref.XServers {
if got, want := len(status.Endpoints), 1; got != want {
t.Errorf("got %d, want %d", got, want)
}
} else {
if got, want := len(status.Endpoints), 0; got != want {
t.Errorf("got %d, want %d", got, want)
}
}
if got, want := len(status.Errors), 1; got != want {
t.Errorf("got %d, want %d", got, want)
}
if got, want := status.Errors[0].Error(), "oops"; !strings.Contains(got, want) {
t.Errorf("got %d, want %d", got, want)
}
}
func TestGranter(t *testing.T) {
ctx, shutdown := test.V23Init()
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: v23.GetPrincipal(cctx).BlessingStore().Default()},
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.V23Init()
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(
v23.GetPrincipal(cctx).BlessingStore().Default(), "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.V23Init()
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, v23.GetPrincipal(scctx).BlessingStore().Default())
}
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.V23Init()
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]
ep.(*inaming.Endpoint).Blessings = []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.V23Init()
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: []*vdl.Value{vdl.StringValue("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 := impetus[len(impetus)-1], test.Impetus; !reflect.DeepEqual(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.V23Init()
defer shutdown()
sctx := withPrincipal(t, ctx, "server")
bserver := v23.GetPrincipal(sctx).BlessingStore().Default()
cctx := withPrincipal(t, ctx, "client")
bclient := v23.GetPrincipal(cctx).BlessingStore().Default()
cctx, err := v23.WithPrincipal(cctx,
&singleBlessingPrincipal{Principal: v23.GetPrincipal(cctx)})
if err != nil {
t.Fatal(err)
}
v23.GetPrincipal(cctx).BlessingStore().SetDefault(bclient)
bvictim := v23.GetPrincipal(withPrincipal(t, ctx, "victim")).BlessingStore().Default()
_, 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.V23Init()
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 TestDischargePurgeFromCache(t *testing.T) {
ctx, shutdown := test.V23Init()
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) {
if ref.RPCTransitionState() != ref.XServers {
t.SkipNow()
}
ctx, shutdown := test.V23Init()
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)
}
}