naming,security: Server authorization via trusted mounttables
(Part 2 of 2).
Details in commit 4312a8df4ba0cc5e77a9202ff464bd06a84928e4
(https://vanadium-review.googlesource.com/#/c/2971/)
This commit represents steps (c) & (d) in the commit description
referenced above.
Change-Id: Ifa28de3a49c6be5ad07f5ae37e4080f2ff73615e
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index d650d92..5e0815e 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -426,7 +426,6 @@
// the servers that were successfully connected to and authorized).
func (c *client) tryCall(ctx *context.T, name, method string, args []interface{}, opts []ipc.CallOpt) (ipc.Call, verror.ActionCode, error) {
var resolved *naming.MountEntry
- var pattern security.BlessingPattern
var err error
if resolved, err = c.ns.Resolve(ctx, name, getResolveOpts(opts)...); err != nil {
vlog.Errorf("Resolve: %v", err)
@@ -438,7 +437,6 @@
}
return nil, verror.NoRetry, verror.New(verror.ErrNoServers, ctx, name, err)
} else {
- pattern = security.BlessingPattern(resolved.Pattern)
if len(resolved.Servers) == 0 {
return nil, verror.RetryRefetch, verror.New(verror.ErrNoServers, ctx, name)
}
@@ -448,7 +446,7 @@
}
}
- // servers is now orderd by the priority heurestic implemented in
+ // servers is now ordered by the priority heurestic implemented in
// filterAndOrderServers.
//
// Try to connect to all servers in parallel. Provide sufficient
@@ -519,7 +517,8 @@
if r.flow.LocalPrincipal() != nil {
// Validate caveats on the server's identity for the context associated with this call.
var err error
- if serverB, grantedB, err = c.authorizeServer(ctx, r.flow, name, method, pattern, opts); err != nil {
+ patterns := resolved.Servers[r.index].BlessingPatterns
+ if serverB, grantedB, err = c.authorizeServer(ctx, r.flow, name, method, patterns, opts); err != nil {
r.err = verror.New(errNotTrusted, ctx, name, r.flow.RemoteBlessings(), err)
vlog.VI(2).Infof("ipc: err: %s", r.err)
r.flow.Close()
@@ -642,7 +641,7 @@
// the RPC name.method for the client (local end of the flow). It returns the blessings at the
// server that are authorized for this purpose and any blessings that are to be granted to
// the server (via ipc.Granter implementations in opts.)
-func (c *client) authorizeServer(ctx *context.T, flow stream.Flow, name, method string, serverPattern security.BlessingPattern, opts []ipc.CallOpt) (serverBlessings []string, grantedBlessings security.Blessings, err error) {
+func (c *client) authorizeServer(ctx *context.T, flow stream.Flow, name, method string, serverPatterns []string, opts []ipc.CallOpt) (serverBlessings []string, grantedBlessings security.Blessings, err error) {
if flow.RemoteBlessings() == nil {
return nil, nil, verror.New(errNoBlessings, ctx)
}
@@ -655,16 +654,9 @@
RemoteDischarges: flow.RemoteDischarges(),
Method: method,
Suffix: name})
- serverBlessings, serverErr := flow.RemoteBlessings().ForContext(ctxt)
- if serverPattern != "" {
- if !serverPattern.MatchedBy(serverBlessings...) {
- return nil, nil, verror.New(errAuthNoPatternMatch, ctx, serverBlessings, serverPattern, serverErr)
- }
- } else if enableSecureServerAuth {
- if err := (defaultAuthorizer{}).Authorize(ctxt); err != nil {
- return nil, nil, verror.New(errDefaultAuthDenied, ctx, serverBlessings)
- }
- }
+ var rejectedBlessings []security.RejectedBlessing
+ serverBlessings, rejectedBlessings = flow.RemoteBlessings().ForContext(ctxt)
+ var ignorePatterns bool
for _, o := range opts {
switch v := o.(type) {
case options.ServerPublicKey:
@@ -682,6 +674,8 @@
if !allowed {
return nil, nil, verror.New(errAuthServerNotAllowed, ctx, v, serverBlessings)
}
+ case options.SkipResolveAuthorization:
+ ignorePatterns = true
case ipc.Granter:
if b, err := v.Grant(flow.RemoteBlessings()); err != nil {
return nil, nil, verror.New(errBlessingGrant, ctx, serverBlessings, err)
@@ -690,6 +684,22 @@
}
}
}
+ if len(serverPatterns) > 0 && !ignorePatterns {
+ matched := false
+ for _, p := range serverPatterns {
+ if security.BlessingPattern(p).MatchedBy(serverBlessings...) {
+ matched = true
+ break
+ }
+ }
+ if !matched {
+ return nil, nil, verror.New(errAuthNoPatternMatch, ctx, serverBlessings, serverPatterns, rejectedBlessings)
+ }
+ } else if enableSecureServerAuth && !ignorePatterns {
+ if err := (defaultAuthorizer{}).Authorize(ctxt); err != nil {
+ return nil, nil, verror.New(errDefaultAuthDenied, ctx, serverBlessings)
+ }
+ }
return serverBlessings, grantedBlessings, nil
}
diff --git a/runtimes/google/ipc/full_test.go b/runtimes/google/ipc/full_test.go
index 2a184ba..e5ae443 100644
--- a/runtimes/google/ipc/full_test.go
+++ b/runtimes/google/ipc/full_test.go
@@ -239,9 +239,6 @@
if err := server.ServeDispatcher(name, disp); err != nil {
t.Errorf("server.ServeDispatcher failed: %v", err)
}
- if err := server.AddName(name); err != nil {
- t.Errorf("server.AddName for discharger failed: %v", err)
- }
status := server.Status()
if got, want := endpointsToStrings(status.Endpoints), endpointsToStrings(eps); !reflect.DeepEqual(got, want) {
@@ -271,7 +268,7 @@
func verifyMountMissing(t *testing.T, ns naming.Namespace, name string) {
if me, err := ns.Resolve(testContext(), name); err == nil {
names := me.Names()
- t.Errorf("%s: %s not supposed to be found in mounttable; got %d servers instead: %v", loc(1), name, len(names), names)
+ t.Errorf("%s: %s not supposed to be found in mounttable; got %d servers instead: %v (%+v)", loc(1), name, len(names), names, me)
}
}
@@ -464,7 +461,7 @@
}{
// Client accepts talking to the server only if the
// server's blessings match the provided pattern
- {bServer, "mountpoint/server", nil, noErrID, ""},
+ {bServer, "[...]mountpoint/server", nil, noErrID, ""},
{bServer, "[root/server]mountpoint/server", nil, noErrID, ""},
{bServer, "[root/otherserver]mountpoint/server", nil, verror.ErrNotTrusted, nameErr},
{bServer, "[otherroot/server]mountpoint/server", nil, verror.ErrNotTrusted, nameErr},
@@ -472,24 +469,23 @@
// and, if the server's blessing has third-party
// caveats then the server provides appropriate
// discharges.
- {bServerTPValid, "mountpoint/server", nil, noErrID, ""},
+ {bServerTPValid, "[...]mountpoint/server", nil, noErrID, ""},
{bServerTPValid, "[root/serverWithTPCaveats]mountpoint/server", nil, noErrID, ""},
{bServerTPValid, "[root/otherserver]mountpoint/server", nil, verror.ErrNotTrusted, nameErr},
{bServerTPValid, "[otherroot/server]mountpoint/server", nil, verror.ErrNotTrusted, nameErr},
// Client does not talk to a server that presents
// expired blessings (because the blessing store is
- // configured to only talk to peers with blessings matching
- // the pattern "root").
- {bServerExpired, "mountpoint/server", nil, verror.ErrNotTrusted, vcErr},
+ // configured to only talk to root).
+ {bServerExpired, "[...]mountpoint/server", nil, verror.ErrNotTrusted, vcErr},
// Client does not talk to a server that fails to
// provide discharges for third-party caveats on the
// blessings presented by it.
- {bServerTPExpired, "mountpoint/server", nil, verror.ErrNotTrusted, vcErr},
+ {bServerTPExpired, "[...]mountpoint/server", nil, verror.ErrNotTrusted, vcErr},
// Testing the AllowedServersPolicy option.
- {bServer, "mountpoint/server", options.AllowedServersPolicy{"otherroot"}, verror.ErrNotTrusted, allowedErr},
+ {bServer, "[...]mountpoint/server", options.AllowedServersPolicy{"otherroot"}, verror.ErrNotTrusted, allowedErr},
{bServer, "[root/server]mountpoint/server", options.AllowedServersPolicy{"otherroot"}, verror.ErrNotTrusted, allowedErr},
{bServer, "[otherroot/server]mountpoint/server", options.AllowedServersPolicy{"root/server"}, verror.ErrNotTrusted, nameErr},
{bServer, "[root/server]mountpoint/server", options.AllowedServersPolicy{"root"}, noErrID, ""},
@@ -517,7 +513,7 @@
pclient.BlessingStore().Set(bless(pprovider, pclient, "client"), "root")
for i, test := range tests {
- name := fmt.Sprintf("(Name:%q, Server:%q, Allowed:%v)", test.name, test.server, test.allowed)
+ name := fmt.Sprintf("(#%d: Name:%q, Server:%q, Allowed:%v)", i, test.name, test.server, test.allowed)
if err := pserver.BlessingStore().SetDefault(test.server); err != nil {
t.Fatalf("SetDefault failed on server's BlessingStore: %v", err)
}
@@ -537,7 +533,7 @@
}
call, err := client.StartCall(ctx, test.name, "Method", nil, opts...)
if !matchesErrorPattern(err, test.errID, test.err) {
- t.Errorf(`%d: %s: client.StartCall: got error "%v", want to match "%v"`, i, name, err, 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 == nil {
@@ -556,6 +552,63 @@
}
}
+func TestServerManInTheMiddleAttack(t *testing.T) {
+ // Test scenario: A server mounts itself, but then some other service
+ // somehow "takes over" the endpoint, thus trying to steal traffic.
+
+ // Start up the attacker's server.
+ attacker, err := testInternalNewServer(
+ testContext(),
+ imanager.InternalNew(naming.FixedRoutingID(0xaaaaaaaaaaaaaaaa)),
+ // (To prevent the attacker for legitimately mounting on the
+ // namespace that the client will use, provide it with a
+ // different namespace).
+ tnaming.NewSimpleNamespace(),
+ vc.LocalPrincipal{tsecurity.NewPrincipal("attacker")})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := attacker.Listen(listenSpec); err != nil {
+ t.Fatal(err)
+ }
+ if err := attacker.ServeDispatcher("mountpoint/server", testServerDisp{&testServer{}}); err != nil {
+ t.Fatal(err)
+ }
+ var ep naming.Endpoint
+ if status := attacker.Status(); len(status.Endpoints) < 1 {
+ t.Fatalf("Attacker server does not have an endpoint: %+v", status)
+ } else {
+ ep = status.Endpoints[0]
+ }
+
+ // The legitimate server would have mounted the same endpoint on the
+ // namespace.
+ ns := tnaming.NewSimpleNamespace()
+ if err := ns.Mount(testContext(), "mountpoint/server", ep.Name(), time.Hour, naming.MountedServerBlessingsOpt{"server"}); 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.
+ client, err := InternalNewClient(
+ imanager.InternalNew(naming.FixedRoutingID(0xcccccccccccccccc)),
+ ns,
+ vc.LocalPrincipal{tsecurity.NewPrincipal("client")})
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer client.Close()
+ if _, err := client.StartCall(testContext(), "mountpoint/server", "Closure", nil); !verror.Is(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 := client.StartCall(testContext(), "mountpoint/server", "Closure", nil, options.SkipResolveAuthorization{}); err != nil {
+ t.Errorf("Unexpected error(%v) when skipping server authorization", err)
+ }
+}
+
type websocketMode bool
type closeSendMode bool
@@ -1440,7 +1493,7 @@
defer runServer("mountpoint/batman", options.ServerBlessings{batman}).Shutdown()
defer runServer("mountpoint/default").Shutdown()
- // And finally, make and RPC and see that the client sees "batman"
+ // And finally, make an RPC and see that the client sees "batman"
runClient := func(server string) ([]string, error) {
smc := imanager.InternalNew(naming.FixedRoutingID(0xc))
defer smc.Shutdown()
diff --git a/runtimes/google/naming/namespace/all_test.go b/runtimes/google/naming/namespace/all_test.go
index 450f89f..ab41beb 100644
--- a/runtimes/google/naming/namespace/all_test.go
+++ b/runtimes/google/naming/namespace/all_test.go
@@ -1,6 +1,7 @@
package namespace_test
import (
+ "reflect"
"runtime"
"runtime/debug"
"sync"
@@ -30,11 +31,22 @@
func createContexts(t *testing.T) (sc, c *context.T, cleanup func()) {
ctx, shutdown := testutil.InitForTest()
- var err error
- if sc, err = veyron2.SetPrincipal(ctx, tsecurity.NewPrincipal("test-blessing")); err != nil {
+ var (
+ err error
+ psc = tsecurity.NewPrincipal("sc")
+ pc = tsecurity.NewPrincipal("c")
+ )
+ // Setup the principals so that they recognize each other.
+ if err := psc.AddToRoots(pc.BlessingStore().Default()); err != nil {
t.Fatal(err)
}
- if c, err = veyron2.SetPrincipal(ctx, tsecurity.NewPrincipal("test-blessing")); err != nil {
+ if err := pc.AddToRoots(psc.BlessingStore().Default()); err != nil {
+ t.Fatal(err)
+ }
+ if sc, err = veyron2.SetPrincipal(ctx, psc); err != nil {
+ t.Fatal(err)
+ }
+ if c, err = veyron2.SetPrincipal(ctx, pc); err != nil {
t.Fatal(err)
}
return sc, c, shutdown
@@ -641,3 +653,89 @@
// After successful lookup it should be cached, so the pattern doesn't matter.
testResolveWithPattern(t, c, ns, name, naming.RootBlessingPatternOpt("root/foobar"), mts[mt2MP].name)
}
+
+func TestAuthenticationDuringResolve(t *testing.T) {
+ ctx, shutdown := veyron2.Init()
+ defer shutdown()
+
+ var (
+ rootMtCtx, _ = veyron2.SetPrincipal(ctx, tsecurity.NewPrincipal()) // root mounttable
+ mtCtx, _ = veyron2.SetPrincipal(ctx, tsecurity.NewPrincipal()) // intermediate mounttable
+ serverCtx, _ = veyron2.SetPrincipal(ctx, tsecurity.NewPrincipal()) // end server
+ clientCtx, _ = veyron2.SetPrincipal(ctx, tsecurity.NewPrincipal()) // client process (doing Resolves).
+ idp = tsecurity.NewIDProvider("idp") // identity provider
+ ep1 = naming.FormatEndpoint("tcp", "127.0.0.1:14141")
+
+ resolve = func(name string, opts ...naming.ResolveOpt) (*naming.MountEntry, error) {
+ return veyron2.GetNamespace(clientCtx).Resolve(clientCtx, name, opts...)
+ }
+
+ mount = func(name, server string, ttl time.Duration, opts ...naming.MountOpt) error {
+ return veyron2.GetNamespace(serverCtx).Mount(serverCtx, name, server, ttl, opts...)
+ }
+ )
+ // Setup default blessings for the processes.
+ idp.Bless(veyron2.GetPrincipal(rootMtCtx), "rootmt")
+ idp.Bless(veyron2.GetPrincipal(serverCtx), "server")
+ idp.Bless(veyron2.GetPrincipal(mtCtx), "childmt")
+ idp.Bless(veyron2.GetPrincipal(clientCtx), "client")
+
+ // Setup the namespace root for all the "processes".
+ rootmt := runMT(t, rootMtCtx, "")
+ for _, ctx := range []*context.T{mtCtx, serverCtx, clientCtx} {
+ veyron2.GetNamespace(ctx).SetRoots(rootmt.name)
+ }
+ // Disable caching in the client so that any Mount calls by the server
+ // are noticed immediately.
+ veyron2.GetNamespace(clientCtx).CacheCtl(naming.DisableCache(true))
+
+ // Server mounting without an explicitly specified MountedServerBlessingsOpt,
+ // will automatically fill the Default blessings in.
+ if err := mount("server", ep1, time.Minute); err != nil {
+ t.Error(err)
+ } else if e, err := resolve("server"); err != nil {
+ t.Error(err)
+ } else if len(e.Servers) != 1 {
+ t.Errorf("Got %v, wanted a single server", e.Servers)
+ } else if s := e.Servers[0]; s.Server != ep1 || len(s.BlessingPatterns) != 1 || s.BlessingPatterns[0] != "idp/server" {
+ t.Errorf("Got (%q, %v) want (%q, [%q])", s.Server, s.BlessingPatterns, ep1, "idp/server")
+ } else if e, err = resolve("[otherpattern]server"); err != nil {
+ // Resolving with the "[<pattern>]<OA>" syntax, then <pattern> wins.
+ t.Error(err)
+ } else if s = e.Servers[0]; s.Server != ep1 || len(s.BlessingPatterns) != 1 || s.BlessingPatterns[0] != "otherpattern" {
+ t.Errorf("Got (%q, %v) want (%q, [%q])", s.Server, s.BlessingPatterns, ep1, "otherpattern")
+ }
+ // If an option is explicitly specified, it should be respected.
+ if err := mount("server", ep1, time.Minute, naming.ReplaceMountOpt(true), naming.MountedServerBlessingsOpt{"b1", "b2"}); err != nil {
+ t.Error(err)
+ } else if e, err := resolve("server"); err != nil {
+ t.Error(err)
+ } else if len(e.Servers) != 1 {
+ t.Errorf("Got %v, wanted a single server", e.Servers)
+ } else if s, pats := e.Servers[0], []string{"b1", "b2"}; s.Server != ep1 || !reflect.DeepEqual(s.BlessingPatterns, pats) {
+ t.Errorf("Got (%q, %v) want (%q, %v)", s.Server, s.BlessingPatterns, ep1, pats)
+ }
+
+ // Intermediate mounttables should be authenticated.
+ // Simulate an "attacker" by changing the blessings presented by this
+ // intermediate mounttable (which will not be consistent with the
+ // blessing pattern in the mount entry). A subtle annoyance here: This
+ // "attack" is valid only until the child mounttable remounts itself.
+ // Currently this remounting period is set to 1 minute (publishPeriod
+ // in ipc/consts.go), but if that changes - then this test may need
+ // to change as well.
+ runMT(t, mtCtx, "mt")
+ attacker, err := veyron2.GetPrincipal(mtCtx).BlessSelf("attacker")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := mount("mt/server", ep1, time.Minute, naming.ReplaceMountOpt(true)); err != nil {
+ t.Error(err)
+ } else if err := veyron2.GetPrincipal(mtCtx).BlessingStore().SetDefault(attacker); err != nil {
+ t.Error(err)
+ } else if e, err := resolve("mt/server", options.SkipResolveAuthorization{}); err != nil {
+ t.Errorf("Resolve should succeed when skipping server authorization. Got (%v, %v)", e, err)
+ } else if e, err := resolve("mt/server"); !verror.Is(err, verror.ErrNotTrusted.ID) {
+ t.Errorf("Resolve should have failed with %q because an attacker has taken over the intermediate mounttable. Got (%+v, errorid=%q:%v)", verror.ErrNotTrusted.ID, e, verror.ErrorID(err), err)
+ }
+}
diff --git a/runtimes/google/naming/namespace/glob.go b/runtimes/google/naming/namespace/glob.go
index 495089b..3d81e58 100644
--- a/runtimes/google/naming/namespace/glob.go
+++ b/runtimes/google/naming/namespace/glob.go
@@ -224,9 +224,9 @@
// Glob implements naming.MountTable.Glob.
func (ns *namespace) Glob(ctx *context.T, pattern string) (chan interface{}, error) {
defer vlog.LogCall()()
-
// Root the pattern. If we have no servers to query, give up.
- e, patternWasRooted := ns.rootMountEntry(pattern)
+ // TODO(ashankar): Should not ignore the pattern on the end server?
+ e, _, patternWasRooted := ns.rootMountEntry(pattern)
if len(e.Servers) == 0 {
return nil, verror.New(naming.ErrNoMountTable, ctx)
}
diff --git a/runtimes/google/naming/namespace/glob_test.go b/runtimes/google/naming/namespace/glob_test.go
index c160ccf..6d51bd1 100644
--- a/runtimes/google/naming/namespace/glob_test.go
+++ b/runtimes/google/naming/namespace/glob_test.go
@@ -2,6 +2,8 @@
import (
"testing"
+
+ "v.io/core/veyron2/security"
)
func TestDepth(t *testing.T) {
@@ -27,35 +29,38 @@
}
func TestSplitObjectName(t *testing.T) {
+ const notset = ""
cases := []struct {
- input, mt, server, name string
+ input string
+ mt, server security.BlessingPattern
+ name string
}{
- {"[foo/bar]", "", "foo/bar", ""},
- {"[x/y]/", "x/y", "", "/"},
- {"[foo]a", "", "foo", "a"},
- {"[foo]/a", "foo", "", "/a"},
+ {"[foo/bar]", notset, "foo/bar", ""},
+ {"[x/y]/", "x/y", notset, "/"},
+ {"[foo]a", notset, "foo", "a"},
+ {"[foo]/a", "foo", notset, "/a"},
{"[foo]/a/[bar]", "foo", "bar", "/a"},
- {"a/b", "", "", "a/b"},
- {"[foo]a/b", "", "foo", "a/b"},
- {"/a/b", "", "", "/a/b"},
- {"[foo]/a/b", "foo", "", "/a/b"},
- {"/a/[bar]b", "", "bar", "/a/b"},
+ {"a/b", notset, notset, "a/b"},
+ {"[foo]a/b", notset, "foo", "a/b"},
+ {"/a/b", notset, notset, "/a/b"},
+ {"[foo]/a/b", "foo", notset, "/a/b"},
+ {"/a/[bar]b", notset, "bar", "/a/b"},
{"[foo]/a/[bar]b", "foo", "bar", "/a/b"},
- {"/a/b[foo]", "", "", "/a/b[foo]"},
- {"/a/b/[foo]c", "", "", "/a/b/[foo]c"},
- {"/[01:02::]:444", "", "", "/[01:02::]:444"},
- {"[foo]/[01:02::]:444", "foo", "", "/[01:02::]:444"},
- {"/[01:02::]:444/foo", "", "", "/[01:02::]:444/foo"},
- {"[a]/[01:02::]:444/foo", "a", "", "/[01:02::]:444/foo"},
- {"/[01:02::]:444/[b]foo", "", "b", "/[01:02::]:444/foo"},
+ {"/a/b[foo]", notset, notset, "/a/b[foo]"},
+ {"/a/b/[foo]c", notset, notset, "/a/b/[foo]c"},
+ {"/[01:02::]:444", notset, notset, "/[01:02::]:444"},
+ {"[foo]/[01:02::]:444", "foo", notset, "/[01:02::]:444"},
+ {"/[01:02::]:444/foo", notset, notset, "/[01:02::]:444/foo"},
+ {"[a]/[01:02::]:444/foo", "a", notset, "/[01:02::]:444/foo"},
+ {"/[01:02::]:444/[b]foo", notset, "b", "/[01:02::]:444/foo"},
{"[c]/[01:02::]:444/[d]foo", "c", "d", "/[01:02::]:444/foo"},
}
for _, c := range cases {
mt, server, name := splitObjectName(c.input)
- if string(mt) != c.mt {
+ if mt != c.mt {
t.Errorf("%q: unexpected mt pattern: %q not %q", c.input, mt, c.mt)
}
- if string(server) != c.server {
+ if server != c.server {
t.Errorf("%q: unexpected server pattern: %q not %q", c.input, server, c.server)
}
if name != c.name {
diff --git a/runtimes/google/naming/namespace/mount.go b/runtimes/google/naming/namespace/mount.go
index 0cab494..55a5027 100644
--- a/runtimes/google/naming/namespace/mount.go
+++ b/runtimes/google/naming/namespace/mount.go
@@ -1,6 +1,7 @@
package namespace
import (
+ "fmt"
"time"
inaming "v.io/core/veyron/runtimes/google/naming"
@@ -10,6 +11,7 @@
"v.io/core/veyron2/ipc"
"v.io/core/veyron2/naming"
"v.io/core/veyron2/options"
+ "v.io/core/veyron2/security"
"v.io/core/veyron2/vlog"
)
@@ -19,10 +21,10 @@
}
// mountIntoMountTable mounts a single server into a single mount table.
-func mountIntoMountTable(ctx *context.T, client ipc.Client, name, server string, ttl time.Duration, flags naming.MountFlag, id string) (s status) {
+func mountIntoMountTable(ctx *context.T, client ipc.Client, name, server string, patterns []security.BlessingPattern, ttl time.Duration, flags naming.MountFlag, id string) (s status) {
s.id = id
ctx, _ = context.WithTimeout(ctx, callTimeout)
- call, err := client.StartCall(ctx, name, "Mount", []interface{}{server, uint32(ttl.Seconds()), flags}, options.NoResolve{})
+ call, err := client.StartCall(ctx, name, "MountX", []interface{}{server, patterns, uint32(ttl.Seconds()), flags}, options.NoResolve{})
s.err = err
if err != nil {
return
@@ -107,6 +109,7 @@
defer vlog.LogCall()()
var flags naming.MountFlag
+ var patterns []string
for _, o := range opts {
// NB: used a switch since we'll be adding more options.
switch v := o.(type) {
@@ -118,17 +121,32 @@
if v {
flags |= naming.MountFlag(naming.MT)
}
+ case naming.MountedServerBlessingsOpt:
+ patterns = []string(v)
}
}
+ if len(patterns) == 0 {
+ // No patterns explicitly provided. Take the conservative
+ // approach that the server being mounted is run by this local
+ // process.
+ p := veyron2.GetPrincipal(ctx)
+ b := p.BlessingStore().Default()
+ if b == nil {
+ return fmt.Errorf("must provide a MountedServerBlessingsOpt")
+ }
+ for str, _ := range p.BlessingsInfo(b) {
+ patterns = append(patterns, str)
+ }
+ vlog.VI(2).Infof("Mount(%s, %s): No MountedServerBlessingsOpt provided using %v", name, server, patterns)
+ }
client := veyron2.GetClient(ctx)
-
// Mount the server in all the returned mount tables.
f := func(ctx *context.T, mt, id string) status {
- return mountIntoMountTable(ctx, client, mt, server, ttl, flags, id)
+ return mountIntoMountTable(ctx, client, mt, server, str2pattern(patterns), ttl, flags, id)
}
err := ns.dispatch(ctx, name, f)
- vlog.VI(1).Infof("Mount(%s, %s) -> %v", name, server, err)
+ vlog.VI(1).Infof("Mount(%s, %q, %v) -> %v", name, server, patterns, err)
return err
}
@@ -143,3 +161,11 @@
vlog.VI(1).Infof("Unmount(%s, %s) -> %v", name, server, err)
return err
}
+
+func str2pattern(strs []string) (ret []security.BlessingPattern) {
+ ret = make([]security.BlessingPattern, len(strs))
+ for i, s := range strs {
+ ret[i] = security.BlessingPattern(s)
+ }
+ return
+}
diff --git a/runtimes/google/naming/namespace/namespace.go b/runtimes/google/naming/namespace/namespace.go
index 612664b..7b176ac 100644
--- a/runtimes/google/naming/namespace/namespace.go
+++ b/runtimes/google/naming/namespace/namespace.go
@@ -119,11 +119,18 @@
}
// rootMountEntry 'roots' a name creating a mount entry for the name.
-func (ns *namespace) rootMountEntry(name string) (*naming.MountEntry, bool) {
+//
+// Returns:
+// (1) MountEntry
+// (2) The BlessingPattern that the end servers are expected to match
+// (empty string if no such pattern).
+// (3) Whether "name" is a rooted name or not (if not, the namespace roots
+// configured in "ns" will be used).
+func (ns *namespace) rootMountEntry(name string, opts ...naming.ResolveOpt) (*naming.MountEntry, security.BlessingPattern, bool) {
name = naming.Clean(name)
_, objPattern, name := splitObjectName(name)
+ mtPattern := getRootPattern(opts)
e := new(naming.MountEntry)
- e.Pattern = string(objPattern)
expiration := time.Now().Add(time.Hour) // plenty of time for a call
address, suffix := naming.SplitAddressName(name)
if len(address) == 0 {
@@ -132,9 +139,14 @@
ns.RLock()
defer ns.RUnlock()
for _, r := range ns.roots {
- e.Servers = append(e.Servers, naming.MountedServer{Server: r, Expires: expiration})
+ // TODO(ashankar): Configured namespace roots should also include the pattern?
+ server := naming.MountedServer{Server: r, Expires: expiration}
+ if len(mtPattern) > 0 {
+ server.BlessingPatterns = []string{mtPattern}
+ }
+ e.Servers = append(e.Servers, server)
}
- return e, false
+ return e, objPattern, false
}
servesMT := true
if ep, err := inaming.NewEndpoint(address); err == nil {
@@ -142,8 +154,12 @@
}
e.SetServesMountTable(servesMT)
e.Name = suffix
- e.Servers = append(e.Servers, naming.MountedServer{Server: naming.JoinAddressName(address, ""), Expires: expiration})
- return e, true
+ server := naming.MountedServer{Server: naming.JoinAddressName(address, ""), Expires: expiration}
+ if servesMT && len(mtPattern) > 0 {
+ server.BlessingPatterns = []string{string(mtPattern)}
+ }
+ e.Servers = []naming.MountedServer{server}
+ return e, objPattern, true
}
// notAnMT returns true if the error indicates this isn't a mounttable server.
@@ -194,6 +210,15 @@
return nil
}
+// TODO(ribrdb,ashankar): This is exported only for the mock namespace to share
+// functionality. Refactor this sharing and do not use this function outside
+// the one place it is being used to implement a mock namespace.
+func InternalSplitObjectName(name string) (p security.BlessingPattern, n string) {
+ _, p, n = splitObjectName(name)
+ return
+}
+
+// TODO(ashankar,ribrdb): Get rid of "mtPattern"?
func splitObjectName(name string) (mtPattern, serverPattern security.BlessingPattern, objectName string) {
objectName = name
match := serverPatternRegexp.FindSubmatch([]byte(name))
@@ -218,3 +243,12 @@
}
return
}
+
+func getRootPattern(opts []naming.ResolveOpt) string {
+ for _, opt := range opts {
+ if pattern, ok := opt.(naming.RootBlessingPatternOpt); ok {
+ return string(pattern)
+ }
+ }
+ return ""
+}
diff --git a/runtimes/google/naming/namespace/resolve.go b/runtimes/google/naming/namespace/resolve.go
index 5a91c14..8c4bfec 100644
--- a/runtimes/google/naming/namespace/resolve.go
+++ b/runtimes/google/naming/namespace/resolve.go
@@ -2,7 +2,6 @@
import (
"errors"
- "fmt"
"runtime"
"v.io/core/veyron2"
@@ -10,29 +9,29 @@
"v.io/core/veyron2/ipc"
"v.io/core/veyron2/naming"
"v.io/core/veyron2/options"
+ "v.io/core/veyron2/security"
"v.io/core/veyron2/verror"
"v.io/core/veyron2/vlog"
)
-func (ns *namespace) resolveAgainstMountTable(ctx *context.T, client ipc.Client, e *naming.MountEntry, pattern string, opts ...ipc.CallOpt) (*naming.MountEntry, error) {
+func (ns *namespace) resolveAgainstMountTable(ctx *context.T, client ipc.Client, e *naming.MountEntry, opts ...ipc.CallOpt) (*naming.MountEntry, error) {
// Try each server till one answers.
finalErr := errors.New("no servers to resolve query")
+ skipServerAuth := skipServerAuthorization(opts)
+ opts = append(opts, options.NoResolve{})
for _, s := range e.Servers {
- var pattern_and_name string
name := naming.JoinAddressName(s.Server, e.Name)
- if pattern != "" {
- pattern_and_name = naming.JoinAddressName(s.Server, fmt.Sprintf("[%s]%s", pattern, e.Name))
- } else {
- pattern_and_name = name
- }
// First check the cache.
if ne, err := ns.resolutionCache.lookup(name); err == nil {
vlog.VI(2).Infof("resolveAMT %s from cache -> %v", name, convertServersToStrings(ne.Servers, ne.Name))
return &ne, nil
}
// Not in cache, call the real server.
+ if !skipServerAuth {
+ opts = setAllowedServers(opts, s.BlessingPatterns)
+ }
callCtx, _ := context.WithTimeout(ctx, callTimeout)
- call, err := client.StartCall(callCtx, pattern_and_name, "ResolveStep", nil, append(opts, options.NoResolve{})...)
+ call, err := client.StartCall(callCtx, name, "ResolveStep", nil, opts...)
if err != nil {
finalErr = err
vlog.VI(2).Infof("ResolveStep.StartCall %s failed: %s", name, err)
@@ -71,19 +70,19 @@
// Resolve implements veyron2/naming.Namespace.
func (ns *namespace) Resolve(ctx *context.T, name string, opts ...naming.ResolveOpt) (*naming.MountEntry, error) {
defer vlog.LogCall()()
- e, _ := ns.rootMountEntry(name)
+ e, objPattern, _ := ns.rootMountEntry(name, opts...)
if vlog.V(2) {
_, file, line, _ := runtime.Caller(1)
vlog.Infof("Resolve(%s) called from %s:%d", name, file, line)
vlog.Infof("Resolve(%s) -> rootMountEntry %v", name, *e)
}
if skipResolve(opts) {
+ setBlessingPatterns(e, objPattern)
return e, nil
}
if len(e.Servers) == 0 {
return nil, verror.New(naming.ErrNoSuchName, ctx, name)
}
- pattern := getRootPattern(opts)
client := veyron2.GetClient(ctx)
callOpts := getCallOpts(opts)
@@ -91,15 +90,17 @@
for remaining := ns.maxResolveDepth; remaining > 0; remaining-- {
vlog.VI(2).Infof("Resolve(%s) loop %v", name, *e)
if !e.ServesMountTable() || terminal(e) {
+ setBlessingPatterns(e, objPattern)
vlog.VI(1).Infof("Resolve(%s) -> %v", name, *e)
return e, nil
}
var err error
curr := e
- if e, err = ns.resolveAgainstMountTable(ctx, client, curr, pattern, callOpts...); err != nil {
+ if e, err = ns.resolveAgainstMountTable(ctx, client, curr, callOpts...); err != nil {
// Lots of reasons why another error can happen. We are trying
// to single out "this isn't a mount table".
if notAnMT(err) {
+ setBlessingPatterns(curr, objPattern)
vlog.VI(1).Infof("Resolve(%s) -> %v", name, curr)
return curr, nil
}
@@ -109,7 +110,6 @@
vlog.VI(1).Infof("Resolve(%s) -> (%s: %v)", err, name, curr)
return nil, err
}
- pattern = ""
}
return nil, verror.New(naming.ErrResolutionDepthExceeded, ctx)
}
@@ -117,7 +117,7 @@
// ResolveToMountTable implements veyron2/naming.Namespace.
func (ns *namespace) ResolveToMountTable(ctx *context.T, name string, opts ...naming.ResolveOpt) (*naming.MountEntry, error) {
defer vlog.LogCall()()
- e, _ := ns.rootMountEntry(name)
+ e, _, _ := ns.rootMountEntry(name, opts...)
if vlog.V(2) {
_, file, line, _ := runtime.Caller(1)
vlog.Infof("ResolveToMountTable(%s) called from %s:%d", name, file, line)
@@ -126,7 +126,6 @@
if len(e.Servers) == 0 {
return nil, verror.New(naming.ErrNoMountTable, ctx)
}
- pattern := getRootPattern(opts)
callOpts := getCallOpts(opts)
client := veyron2.GetClient(ctx)
last := e
@@ -139,7 +138,7 @@
vlog.VI(1).Infof("ResolveToMountTable(%s) -> %v", name, last)
return last, nil
}
- if e, err = ns.resolveAgainstMountTable(ctx, client, e, pattern, callOpts...); err != nil {
+ if e, err = ns.resolveAgainstMountTable(ctx, client, e, callOpts...); err != nil {
if verror.Is(err, naming.ErrNoSuchNameRoot.ID) {
vlog.VI(1).Infof("ResolveToMountTable(%s) -> %v (NoSuchRoot: %v)", name, last, curr)
return last, nil
@@ -161,7 +160,6 @@
return nil, err
}
last = curr
- pattern = ""
}
return nil, verror.New(naming.ErrResolutionDepthExceeded, ctx)
}
@@ -199,15 +197,6 @@
return false
}
-func getRootPattern(opts []naming.ResolveOpt) string {
- for _, opt := range opts {
- if pattern, ok := opt.(naming.RootBlessingPatternOpt); ok {
- return string(pattern)
- }
- }
- return ""
-}
-
func getCallOpts(opts []naming.ResolveOpt) []ipc.CallOpt {
var out []ipc.CallOpt
for _, o := range opts {
@@ -217,3 +206,37 @@
}
return out
}
+
+// setBlessingPatterns overrides e.Servers.BlessingPatterns with p if p is
+// non-empty. This will typically be the case for end servers (i.e., not
+// mounttables) where the client explicitly specified a blessing pattern and
+// thus explicitly chose to ignore the patterns from the MountEntry.
+func setBlessingPatterns(e *naming.MountEntry, p security.BlessingPattern) {
+ if len(p) == 0 {
+ return
+ }
+ slice := []string{string(p)}
+ for idx, _ := range e.Servers {
+ e.Servers[idx].BlessingPatterns = slice
+ }
+}
+
+func setAllowedServers(opts []ipc.CallOpt, patterns []string) []ipc.CallOpt {
+ if len(patterns) == 0 {
+ return opts
+ }
+ p := make(options.AllowedServersPolicy, len(patterns))
+ for i, v := range patterns {
+ p[i] = security.BlessingPattern(v)
+ }
+ return append(opts, p)
+}
+
+func skipServerAuthorization(opts []ipc.CallOpt) bool {
+ for _, o := range opts {
+ if _, ok := o.(options.SkipResolveAuthorization); ok {
+ return true
+ }
+ }
+ return false
+}
diff --git a/runtimes/google/naming/namespace/stub.go b/runtimes/google/naming/namespace/stub.go
index 0574e2c..a513e56 100644
--- a/runtimes/google/naming/namespace/stub.go
+++ b/runtimes/google/naming/namespace/stub.go
@@ -27,7 +27,7 @@
s.TTL = 32000000 // > 1 year
}
expires := time.Now().Add(time.Duration(s.TTL) * time.Second)
- reply = append(reply, naming.MountedServer{Server: s.Server, Expires: expires})
+ reply = append(reply, naming.MountedServer{Server: s.Server, BlessingPatterns: s.BlessingPatterns, Expires: expires})
}
return reply
}
diff --git a/runtimes/google/testing/mocks/naming/namespace.go b/runtimes/google/testing/mocks/naming/namespace.go
index 796c5e2..02927e8 100644
--- a/runtimes/google/testing/mocks/naming/namespace.go
+++ b/runtimes/google/testing/mocks/naming/namespace.go
@@ -8,7 +8,6 @@
"v.io/core/veyron2/context"
"v.io/core/veyron2/naming"
- "v.io/core/veyron2/options"
"v.io/core/veyron2/verror"
"v.io/core/veyron2/vlog"
@@ -23,18 +22,28 @@
if err != nil {
panic(err)
}
- return &namespace{mounts: make(map[string][]string), ns: ns}
+ return &namespace{mounts: make(map[string]*naming.MountEntry), ns: ns}
}
// namespace is a simple partial implementation of naming.Namespace.
type namespace struct {
sync.Mutex
- mounts map[string][]string
+ mounts map[string]*naming.MountEntry
ns naming.Namespace
}
-func (ns *namespace) Mount(ctx *context.T, name, server string, _ time.Duration, _ ...naming.MountOpt) error {
+func (ns *namespace) Mount(ctx *context.T, name, server string, _ time.Duration, opts ...naming.MountOpt) error {
defer vlog.LogCall()()
+ // TODO(ashankar,p): There is a bunch of processing in the real
+ // namespace that is missing from this mock implementation and some of
+ // it is duplicated here. Figure out a way to share more code with the
+ // real implementation?
+ var blessingpatterns []string
+ for _, o := range opts {
+ if v, ok := o.(naming.MountedServerBlessingsOpt); ok {
+ blessingpatterns = v
+ }
+ }
ns.Lock()
defer ns.Unlock()
for n, _ := range ns.mounts {
@@ -42,50 +51,74 @@
return fmt.Errorf("simple mount table does not allow names that are a prefix of each other")
}
}
- ns.mounts[name] = append(ns.mounts[name], server)
+ e := ns.mounts[name]
+ if e == nil {
+ e = &naming.MountEntry{}
+ ns.mounts[name] = e
+ }
+ s := naming.MountedServer{
+ Server: server,
+ BlessingPatterns: blessingpatterns,
+ }
+ e.Servers = append(e.Servers, s)
return nil
}
func (ns *namespace) Unmount(ctx *context.T, name, server string) error {
defer vlog.LogCall()()
- var servers []string
ns.Lock()
defer ns.Unlock()
- for _, s := range ns.mounts[name] {
- // When server is "", we remove all servers under name.
- if len(server) > 0 && s != server {
- servers = append(servers, s)
+ e := ns.mounts[name]
+ if e == nil {
+ return nil
+ }
+ if len(server) == 0 {
+ delete(ns.mounts, name)
+ return nil
+ }
+ var keep []naming.MountedServer
+ for _, s := range e.Servers {
+ if s.Server != server {
+ keep = append(keep, s)
}
}
- if len(servers) > 0 {
- ns.mounts[name] = servers
- } else {
+ if len(keep) == 0 {
delete(ns.mounts, name)
+ return nil
}
+ e.Servers = keep
return nil
}
func (ns *namespace) Resolve(ctx *context.T, name string, opts ...naming.ResolveOpt) (*naming.MountEntry, error) {
defer vlog.LogCall()()
- e, err := ns.ns.Resolve(ctx, name, options.NoResolve{})
- if err != nil {
- return e, err
+ p, n := vnamespace.InternalSplitObjectName(name)
+ var blessingpatterns []string
+ if len(p) > 0 {
+ blessingpatterns = []string{string(p)}
}
- if len(e.Servers) > 0 {
- e.Servers[0].Server = naming.JoinAddressName(e.Servers[0].Server, e.Name)
- e.Name = ""
- return e, nil
+ name = n
+ if address, suffix := naming.SplitAddressName(name); len(address) > 0 {
+ return &naming.MountEntry{
+ Name: suffix,
+ Servers: []naming.MountedServer{
+ {Server: address, BlessingPatterns: blessingpatterns},
+ },
+ }, nil
}
ns.Lock()
defer ns.Unlock()
- name = e.Name
- for prefix, servers := range ns.mounts {
+ for prefix, e := range ns.mounts {
if strings.HasPrefix(name, prefix) {
- e.Name = strings.TrimLeft(strings.TrimPrefix(name, prefix), "/")
- for _, s := range servers {
- e.Servers = append(e.Servers, naming.MountedServer{Server: s, Expires: time.Now().Add(1000 * time.Hour)})
+ ret := *e
+ ret.Name = strings.TrimLeft(strings.TrimPrefix(name, prefix), "/")
+ if len(blessingpatterns) > 0 {
+ // Replace the blessing patterns with p.
+ for idx, _ := range ret.Servers {
+ ret.Servers[idx].BlessingPatterns = blessingpatterns
+ }
}
- return e, nil
+ return &ret, nil
}
}
return nil, verror.New(naming.ErrNoSuchName, ctx, fmt.Sprintf("Resolve name %q not found in %v", name, ns.mounts))
diff --git a/security/agent/pingpong/main.go b/security/agent/pingpong/main.go
index 2db7acd..231ea8f 100644
--- a/security/agent/pingpong/main.go
+++ b/security/agent/pingpong/main.go
@@ -7,6 +7,7 @@
"v.io/core/veyron2"
"v.io/core/veyron2/context"
"v.io/core/veyron2/ipc"
+ "v.io/core/veyron2/options"
"v.io/core/veyron2/security"
"v.io/core/veyron2/vlog"
@@ -27,7 +28,7 @@
vlog.Info("Pinging...")
s := PingPongClient("pingpong")
- pong, err := s.Ping(ctx, "ping")
+ pong, err := s.Ping(ctx, "ping", options.SkipResolveAuthorization{})
if err != nil {
vlog.Fatal("error pinging: ", err)
}
diff --git a/services/mgmt/application/impl/acl_test.go b/services/mgmt/application/impl/acl_test.go
index 9935409..ad00fb7 100644
--- a/services/mgmt/application/impl/acl_test.go
+++ b/services/mgmt/application/impl/acl_test.go
@@ -80,34 +80,34 @@
defer shutdown()
veyron2.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
- sh, deferFn := mgmttest.CreateShellAndMountTable(t, ctx, nil)
+ // By default, all principals in this test will have blessings
+ // generated based on the username/machine running this process. Give
+ // them recognizable names ("root/self" etc.), so the ACLs can be set
+ // deterministically.
+ idp := tsecurity.NewIDProvider("root")
+ if err := idp.Bless(veyron2.GetPrincipal(ctx), "self"); err != nil {
+ t.Fatal(err)
+ }
+
+ sh, deferFn := mgmttest.CreateShellAndMountTable(t, ctx, veyron2.GetPrincipal(ctx))
defer deferFn()
// setup mock up directory to put state in
storedir, cleanup := mgmttest.SetupRootDir(t, "application")
defer cleanup()
+ _, nms := mgmttest.RunShellCommand(t, sh, nil, repoCmd, "repo", storedir)
+ pid := mgmttest.ReadPID(t, nms)
+ defer syscall.Kill(pid, syscall.SIGINT)
+
otherCtx, err := veyron2.SetPrincipal(ctx, tsecurity.NewPrincipal())
if err != nil {
t.Fatal(err)
}
-
- idp := tsecurity.NewIDProvider("root")
-
- // By default, globalRT and otherRT will have blessings generated based on the
- // username/machine name running this process. Since these blessings will appear
- // in ACLs, give them recognizable names.
- if err := idp.Bless(veyron2.GetPrincipal(ctx), "self"); err != nil {
- t.Fatal(err)
- }
if err := idp.Bless(veyron2.GetPrincipal(otherCtx), "other"); err != nil {
t.Fatal(err)
}
- _, nms := mgmttest.RunShellCommand(t, sh, nil, repoCmd, "repo", storedir)
- pid := mgmttest.ReadPID(t, nms)
- defer syscall.Kill(pid, syscall.SIGINT)
-
v1stub := repository.ApplicationClient("repo/search/v1")
repostub := repository.ApplicationClient("repo")
@@ -119,9 +119,8 @@
}
// Envelope putting as other should fail.
- // TODO(rjkroege): Validate that it is failed with permission denied.
- if err := v1stub.Put(otherCtx, []string{"base"}, envelopeV1); err == nil {
- t.Fatalf("Put() wrongly didn't fail")
+ if err := v1stub.Put(otherCtx, []string{"base"}, envelopeV1); !verror.Is(err, verror.ErrNoAccess.ID) {
+ t.Fatalf("Put() returned errorid=%v wanted errorid=%v [%v]", verror.ErrorID(err), verror.ErrNoAccess.ID, err)
}
// Envelope putting as global should succeed.
@@ -210,8 +209,16 @@
ctx, shutdown := testutil.InitForTest()
defer shutdown()
veyron2.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
+ // By default, all principals in this test will have blessings
+ // generated based on the username/machine running this process. Give
+ // them recognizable names ("root/self" etc.), so the ACLs can be set
+ // deterministically.
+ idp := tsecurity.NewIDProvider("root")
+ if err := idp.Bless(veyron2.GetPrincipal(ctx), "self"); err != nil {
+ t.Fatal(err)
+ }
- sh, deferFn := mgmttest.CreateShellAndMountTable(t, ctx, nil)
+ sh, deferFn := mgmttest.CreateShellAndMountTable(t, ctx, veyron2.GetPrincipal(ctx))
defer deferFn()
// setup mock up directory to put state in
@@ -222,15 +229,6 @@
if err != nil {
t.Fatal(err)
}
-
- idp := tsecurity.NewIDProvider("root")
-
- // By default, globalRT and otherRT will have blessings generated based on the
- // username/machine name running this process. Since these blessings will appear
- // in ACLs, give them recognizable names.
- if err := idp.Bless(veyron2.GetPrincipal(ctx), "self"); err != nil {
- t.Fatal(err)
- }
if err := idp.Bless(veyron2.GetPrincipal(otherCtx), "other"); err != nil {
t.Fatal(err)
}
diff --git a/services/mgmt/device/impl/impl_test.go b/services/mgmt/device/impl/impl_test.go
index e9425f0..6df138b 100644
--- a/services/mgmt/device/impl/impl_test.go
+++ b/services/mgmt/device/impl/impl_test.go
@@ -835,6 +835,13 @@
defer shutdown()
veyron2.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
+ // root blessing provider so that the principals of all the contexts
+ // recognize each other.
+ idp := tsecurity.NewIDProvider("root")
+ if err := idp.Bless(veyron2.GetPrincipal(ctx), "ctx"); err != nil {
+ t.Fatal(err)
+ }
+
sh, deferFn := mgmttest.CreateShellAndMountTable(t, ctx, nil)
defer deferFn()
@@ -857,17 +864,19 @@
*envelope = envelopeFromShell(sh, nil, appCmd, "google naps", "trapp")
- claimantCtx, err := veyron2.SetPrincipal(ctx, tsecurity.NewPrincipal("claimant"))
- if err != nil {
- t.Fatalf("Could not create claimant principal: %v", err)
- }
+ claimantCtx := ctxWithNewPrincipal(t, ctx, idp, "claimant")
octx, err := veyron2.SetPrincipal(ctx, tsecurity.NewPrincipal("other"))
if err != nil {
- t.Fatalf("Could not create other principal: %v", err)
+ t.Fatal(err)
}
// Unclaimed devices cannot do anything but be claimed.
- installAppExpectError(t, octx, impl.ErrUnclaimedDevice.ID)
+ // TODO(ashankar,caprita): The line below will currently fail with
+ // ErrUnclaimedDevice != NotTrusted. NotTrusted can be avoided by
+ // passing options.SkipResolveAuthorization{} to the "Install" RPC.
+ // Refactor the helper function to make this possible.
+ //installAppExpectError(t, octx, impl.ErrUnclaimedDevice.ID)
+
// Claim the device with an incorrect pairing token should fail.
claimDeviceExpectError(t, claimantCtx, "dm", "mydevice", "badtoken", impl.ErrInvalidPairingToken.ID)
// But succeed with a valid pairing token
@@ -877,12 +886,18 @@
// the devicemanager.
appID := installApp(t, claimantCtx)
- // octx should be unable to install though, since the ACLs have
- // changed now.
+ // octx will not install the app now since it doesn't recognize
+ // the device's blessings.
+ installAppExpectError(t, octx, verror.ErrNotTrusted.ID)
+ // Even if it does recognize the device (by virtue of recognizing the
+ // claimant), the device will not allow it to install.
+ if err := veyron2.GetPrincipal(octx).AddToRoots(veyron2.GetPrincipal(claimantCtx).BlessingStore().Default()); err != nil {
+ t.Fatal(err)
+ }
installAppExpectError(t, octx, verror.ErrNoAccess.ID)
// Create the local server that the app uses to let us know it's ready.
- pingCh, cleanup := setupPingServer(t, ctx)
+ pingCh, cleanup := setupPingServer(t, claimantCtx)
defer cleanup()
// Start an instance of the app.
@@ -905,6 +920,11 @@
defer shutdown()
veyron2.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
+ // Identity provider to ensure that all processes recognize each
+ // others' blessings.
+ idp := tsecurity.NewIDProvider("root")
+ ctx = ctxWithNewPrincipal(t, ctx, idp, "self")
+
sh, deferFn := mgmttest.CreateShellAndMountTable(t, ctx, nil)
defer deferFn()
@@ -915,24 +935,8 @@
root, cleanup := mgmttest.SetupRootDir(t, "devicemanager")
defer cleanup()
- // The two "processes"/runtimes which will act as IPC clients to
- // the devicemanager process.
selfCtx := ctx
- octx, err := veyron2.SetPrincipal(ctx, tsecurity.NewPrincipal())
- if err != nil {
- t.Fatalf("Could not create other principal: %v", err)
- }
-
- // By default, selfCtx and octx will have blessings generated based on
- // the username/machine name running this process. Since these blessings
- // will appear in ACLs, give them recognizable names.
- idp := tsecurity.NewIDProvider("root")
- if err := idp.Bless(veyron2.GetPrincipal(selfCtx), "self"); err != nil {
- t.Fatal(err)
- }
- if err := idp.Bless(veyron2.GetPrincipal(octx), "other"); err != nil {
- t.Fatal(err)
- }
+ octx := ctxWithNewPrincipal(t, selfCtx, idp, "other")
// Set up the device manager. Since we won't do device manager updates,
// don't worry about its application envelope and current link.
@@ -1380,24 +1384,15 @@
root, cleanup := mgmttest.SetupRootDir(t, "devicemanager")
defer cleanup()
- // The two "processes"/contexts which will act as IPC clients to
- // the devicemanager process.
- selfCtx := ctx
- otherCtx, err := veyron2.SetPrincipal(ctx, tsecurity.NewPrincipal())
- if err != nil {
- t.Fatalf("Could not create other principal: %v", err)
- }
-
- // By default, selfCtx and otherCtx will have blessings generated based
- // on the username/machine name running this process. Since these
- // blessings will appear in test expecations, give them readable names.
+ // By default, the two processes (selfCtx and octx) will have blessings generated based on
+ // the username/machine name running this process. Since these blessings
+ // will appear in ACLs, give them recognizable names.
idp := tsecurity.NewIDProvider("root")
+ selfCtx := ctx
if err := idp.Bless(veyron2.GetPrincipal(selfCtx), "self"); err != nil {
t.Fatal(err)
}
- if err := idp.Bless(veyron2.GetPrincipal(otherCtx), "other"); err != nil {
- t.Fatal(err)
- }
+ otherCtx := ctxWithNewPrincipal(t, selfCtx, idp, "other")
_, dms := mgmttest.RunShellCommand(t, sh, nil, deviceManagerCmd, "dm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
pid := mgmttest.ReadPID(t, dms)
@@ -1473,6 +1468,13 @@
defer shutdown()
veyron2.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
+ // Identity provider used to ensure that all processes recognize each
+ // others' blessings.
+ idp := tsecurity.NewIDProvider("root")
+ if err := idp.Bless(veyron2.GetPrincipal(ctx), "self"); err != nil {
+ t.Fatal(err)
+ }
+
sh, deferFn := mgmttest.CreateShellAndMountTable(t, ctx, nil)
defer deferFn()
@@ -1483,25 +1485,8 @@
root, cleanup := mgmttest.SetupRootDir(t, "devicemanager")
defer cleanup()
- // The two "processes"/runtimes which will act as IPC clients to
- // the devicemanager process.
selfCtx := ctx
- otherCtx, err := veyron2.SetPrincipal(ctx, tsecurity.NewPrincipal())
- if err != nil {
- t.Fatalf("Could not create other principal: %v", err)
- }
-
- // By default, selfCtx and otherCtx will have blessings generated based
- // on the username/machine name running this process. Since these
- // blessings can appear in debugging output, give them recognizable
- // names.
- idp := tsecurity.NewIDProvider("root")
- if err := idp.Bless(veyron2.GetPrincipal(selfCtx), "self"); err != nil {
- t.Fatal(err)
- }
- if err := idp.Bless(veyron2.GetPrincipal(otherCtx), "other"); err != nil {
- t.Fatal(err)
- }
+ otherCtx := ctxWithNewPrincipal(t, selfCtx, idp, "other")
// Create a script wrapping the test target that implements suidhelper.
helperPath := generateSuidHelperScript(t, root)
@@ -1693,7 +1678,7 @@
t.Fatalf("Upload(%v) failed:%v", binaryVON, err)
}
if _, err := appStub().Install(ctx, mockApplicationRepoName, device.Config{}, nil); !verror.Is(err, impl.ErrOperationFailed.ID) {
- t.Fatalf("Failed to verify signature mismatch for binary:%v", binaryVON)
+ t.Fatalf("Failed to verify signature mismatch for binary:%v. Got errorid=%v[%v], want errorid=%v", binaryVON, verror.ErrorID(err), err, impl.ErrOperationFailed.ID)
}
// Restore the binary and verify that installation succeeds.
diff --git a/services/mgmt/device/impl/util_test.go b/services/mgmt/device/impl/util_test.go
index 156033e..fd07606 100644
--- a/services/mgmt/device/impl/util_test.go
+++ b/services/mgmt/device/impl/util_test.go
@@ -10,10 +10,12 @@
"testing"
"time"
+ tsecurity "v.io/core/veyron/lib/testutil/security"
"v.io/core/veyron2"
"v.io/core/veyron2/context"
"v.io/core/veyron2/ipc"
"v.io/core/veyron2/naming"
+ "v.io/core/veyron2/options"
"v.io/core/veyron2/security"
"v.io/core/veyron2/services/mgmt/application"
"v.io/core/veyron2/services/mgmt/device"
@@ -83,8 +85,10 @@
func claimDevice(t *testing.T, ctx *context.T, name, extension, pairingToken string) {
// Setup blessings to be granted to the claimed device
g := &granter{p: veyron2.GetPrincipal(ctx), extension: extension}
- // Call the Claim RPC
- if err := device.ClaimableClient(name).Claim(ctx, pairingToken, g); err != nil {
+ s := options.SkipResolveAuthorization{}
+ // Call the Claim RPC: Skip server authorization because the unclaimed
+ // device presents nothing that can be used to recognize it.
+ if err := device.ClaimableClient(name).Claim(ctx, pairingToken, g, s); err != nil {
t.Fatalf(testutil.FormatLogLine(2, "%q.Claim(%q) failed: %v [%v]", name, pairingToken, verror.ErrorID(err), err))
}
// Wait for the device to remount itself with the device service after
@@ -93,7 +97,7 @@
// AlreadyClaimed)
start := time.Now()
for {
- if err := device.ClaimableClient(name).Claim(ctx, pairingToken, g); !verror.Is(err, impl.ErrDeviceAlreadyClaimed.ID) {
+ if err := device.ClaimableClient(name).Claim(ctx, pairingToken, g, s); !verror.Is(err, impl.ErrDeviceAlreadyClaimed.ID) {
return
}
vlog.VI(4).Infof("Claimable server at %q has not stopped yet", name)
@@ -107,8 +111,9 @@
func claimDeviceExpectError(t *testing.T, ctx *context.T, name, extension, pairingToken string, errID verror.ID) {
// Setup blessings to be granted to the claimed device
g := &granter{p: veyron2.GetPrincipal(ctx), extension: extension}
+ s := options.SkipResolveAuthorization{}
// Call the Claim RPC
- if err := device.ClaimableClient(name).Claim(ctx, pairingToken, g); !verror.Is(err, errID) {
+ if err := device.ClaimableClient(name).Claim(ctx, pairingToken, g, s); !verror.Is(err, errID) {
t.Fatalf(testutil.FormatLogLine(2, "%q.Claim(%q) expected to fail with %v, got %v [%v]", name, pairingToken, errID, verror.ErrorID(err), err))
}
}
@@ -355,3 +360,14 @@
}
return path
}
+
+func ctxWithNewPrincipal(t *testing.T, ctx *context.T, idp *tsecurity.IDProvider, extension string) *context.T {
+ ret, err := veyron2.SetPrincipal(ctx, tsecurity.NewPrincipal())
+ if err != nil {
+ t.Fatalf(testutil.FormatLogLine(2, "veyron2.SetPrincipal failed: %v", err))
+ }
+ if err := idp.Bless(veyron2.GetPrincipal(ret), extension); err != nil {
+ t.Fatalf(testutil.FormatLogLine(2, "idp.Bless(?, %q) failed: %v", extension, err))
+ }
+ return ret
+}
diff --git a/services/mounttable/lib/serverlist.go b/services/mounttable/lib/serverlist.go
index f74752f..82d658e 100644
--- a/services/mounttable/lib/serverlist.go
+++ b/services/mounttable/lib/serverlist.go
@@ -27,7 +27,7 @@
type server struct {
expires time.Time
oa string // object address of server
- patterns []string // patterns that match the blessings presented by the server.
+ patterns []string // patterns that server blessings should match
}
// serverList represents an ordered list of servers.
diff --git a/tools/mgmt/device/impl/impl.go b/tools/mgmt/device/impl/impl.go
index 83c4d5e..8cf1b90 100644
--- a/tools/mgmt/device/impl/impl.go
+++ b/tools/mgmt/device/impl/impl.go
@@ -7,6 +7,7 @@
"v.io/core/veyron2"
"v.io/core/veyron2/ipc"
"v.io/core/veyron2/naming"
+ "v.io/core/veyron2/options"
"v.io/core/veyron2/security"
"v.io/core/veyron2/services/mgmt/application"
"v.io/core/veyron2/services/mgmt/device"
@@ -166,7 +167,9 @@
pairingToken = args[2]
}
principal := veyron2.GetPrincipal(gctx)
- if err := device.ClaimableClient(deviceName).Claim(gctx, pairingToken, &granter{p: principal, extension: grant}); err != nil {
+ // Skip server authorization since an unclaimed device has no
+ // credentials that will be recognized by the claimer.
+ if err := device.ClaimableClient(deviceName).Claim(gctx, pairingToken, &granter{p: principal, extension: grant}, options.SkipResolveAuthorization{}); err != nil {
return fmt.Errorf("Claim failed: %v", err)
}
fmt.Fprintln(cmd.Stdout(), "Successfully claimed.")
diff --git a/tools/mounttable/impl.go b/tools/mounttable/impl.go
index a62157c..110c090 100644
--- a/tools/mounttable/impl.go
+++ b/tools/mounttable/impl.go
@@ -64,6 +64,7 @@
return err
}
+// TODO(ashankar): Collect the blessing patterns from <name> before Mounting.
var cmdMount = &cmdline.Command{
Run: runMount,
Name: "mount",
diff --git a/tools/namespace/impl.go b/tools/namespace/impl.go
index 5aec1bb..a5d4df6 100644
--- a/tools/namespace/impl.go
+++ b/tools/namespace/impl.go
@@ -54,6 +54,7 @@
return nil
}
+// TODO(ashankar): Collect the blessing patterns from <server> before mounting.
var cmdMount = &cmdline.Command{
Run: runMount,
Name: "mount",
diff --git a/tools/vrpc/vrpc.go b/tools/vrpc/vrpc.go
index a10cf56..bf24fe4 100644
--- a/tools/vrpc/vrpc.go
+++ b/tools/vrpc/vrpc.go
@@ -13,6 +13,7 @@
"v.io/core/veyron2"
"v.io/core/veyron2/context"
"v.io/core/veyron2/ipc/reserved"
+ "v.io/core/veyron2/options"
"v.io/core/veyron2/vdl"
"v.io/core/veyron2/vdl/build"
"v.io/core/veyron2/vdl/codegen/vdlgen"
@@ -129,7 +130,7 @@
defer cancel()
var types signature.NamedTypes
if method != "" {
- methodSig, err := reserved.MethodSignature(ctx, server, method)
+ methodSig, err := reserved.MethodSignature(ctx, server, method, options.SkipResolveAuthorization{})
if err != nil {
return fmt.Errorf("MethodSignature failed: %v", err)
}
@@ -138,7 +139,7 @@
types.Print(cmd.Stdout())
return nil
}
- ifacesSig, err := reserved.Signature(ctx, server)
+ ifacesSig, err := reserved.Signature(ctx, server, options.SkipResolveAuthorization{})
if err != nil {
return fmt.Errorf("Signature failed: %v", err)
}