veyron/services/mgmt/node/impl: make the app be able to talk to the node manager
(and vice-versa) after the node has been claimed.
We need to ensure that the authorizer for the config service is different from
that of the rest of the node, and in particular accepts rpcs from a child that
has been blessed by the node.
Change-Id: I2cea468ea1fe8e962dbb44180cd6ea0bdade61cf
diff --git a/services/mgmt/node/impl/app_invoker.go b/services/mgmt/node/impl/app_invoker.go
index 35a8268..8e4cdb6 100644
--- a/services/mgmt/node/impl/app_invoker.go
+++ b/services/mgmt/node/impl/app_invoker.go
@@ -488,7 +488,7 @@
return errOperationFailed
}
// Wait for the child process to start.
- timeout := 20 * time.Second
+ timeout := 10 * time.Second
if err := handle.WaitForReady(timeout); err != nil {
vlog.Errorf("WaitForReady(%v) failed: %v", timeout, err)
return errOperationFailed
diff --git a/services/mgmt/node/impl/dispatcher.go b/services/mgmt/node/impl/dispatcher.go
index b63ea47..ed930d9 100644
--- a/services/mgmt/node/impl/dispatcher.go
+++ b/services/mgmt/node/impl/dispatcher.go
@@ -228,31 +228,42 @@
// The implementation of the node manager is split up into several
// invokers, which are instantiated depending on the receiver name
// prefix.
- var receiver interface{}
switch components[0] {
case nodeSuffix:
- receiver = node.NewServerNode(&nodeInvoker{
+ receiver := node.NewServerNode(&nodeInvoker{
callback: d.internal.callback,
updating: d.internal.updating,
config: d.config,
disp: d,
})
+ return ipc.ReflectInvoker(receiver), d.auth, nil
case appsSuffix:
- receiver = node.NewServerApplication(&appInvoker{
+ receiver := node.NewServerApplication(&appInvoker{
callback: d.internal.callback,
config: d.config,
suffix: components[1:],
})
+ // TODO(caprita,rjkroege): Once we implement per-object ACLs
+ // (i.e. each installation and instance), replace d.auth with
+ // per-object authorizer.
+ return ipc.ReflectInvoker(receiver), d.auth, nil
case configSuffix:
if len(components) != 2 {
return nil, nil, errInvalidSuffix
}
- receiver = inode.NewServerConfig(&configInvoker{
+ receiver := inode.NewServerConfig(&configInvoker{
callback: d.internal.callback,
suffix: components[1],
})
+ // The nil authorizer ensures that only principals blessed by
+ // the node manager can talk back to it. All apps started by
+ // the node manager should fall in that category.
+ //
+ // TODO(caprita,rjkroege): We should further refine this, by
+ // only allowing the app to update state referring to itself
+ // (and not other apps).
+ return ipc.ReflectInvoker(receiver), nil, nil
default:
return nil, nil, errInvalidSuffix
}
- return ipc.ReflectInvoker(receiver), d.auth, nil
}
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index ee410d8..32458f4 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -772,8 +772,13 @@
readPID(t, nm)
// Create an envelope for an app.
- app := blackbox.HelperCommand(t, "app", "")
- defer setupChildCommand(app)()
+ app := blackbox.HelperCommand(t, "app", "trapp")
+ // Ensure the child has a blessing that (a) allows it talk back to the
+ // node manager config service and (b) allows the node manager to talk
+ // to the app. We achieve this by making the app's blessing derived
+ // from the node manager's blessing post claiming (which will be
+ // "mydevice").
+ defer setupChildCommandWithBlessing(app, "mydevice/child")()
appTitle := "google naps"
*envelope = *envelopeFromCmd(appTitle, app.Cmd)
@@ -794,14 +799,33 @@
if err = nodeStub.Claim(selfRT.NewContext(), &granter{p: selfRT.Principal(), extension: "mydevice"}); err != nil {
t.Fatal(err)
}
- // Installation should succeed since selfRT is now the "owner" of the nodemanager.
- if err = tryInstall(selfRT); err != nil {
- t.Fatal(err)
- }
+ // Installation should succeed since rt.R() (a.k.a. selfRT) is now the
+ // "owner" of the nodemanager.
+ appID := installApp(t)
+
// otherRT should be unable to install though, since the ACLs have changed now.
if err = tryInstall(otherRT); err == nil {
t.Fatalf("Install should have failed from otherRT")
}
+
+ // Create a script wrapping the test target that implements suidhelper.
+ generateSuidHelperScript(t, root)
+
+ // Create the local server that the app uses to let us know it's ready.
+ // TODO(caprita): Factor this code snippet out, it's pretty common.
+ server, _ := newServer()
+ defer server.Stop()
+ pingCh := make(chan string, 1)
+ if err := server.Serve("pingserver", ipc.LeafDispatcher(pingServerDisp(pingCh), nil)); err != nil {
+ t.Fatalf("Serve(%q, <dispatcher>) failed: %v", "pingserver", err)
+ }
+
+ // Start an instance of the app.
+ instanceID := startApp(t, appID)
+ <-pingCh
+ resolve(t, "trapp", 1)
+ suspendApp(t, appID, instanceID)
+
// TODO(gauthamt): Test that ACLs persist across nodemanager restarts
}
diff --git a/services/mgmt/node/impl/util_test.go b/services/mgmt/node/impl/util_test.go
index b519965..37d3cfb 100644
--- a/services/mgmt/node/impl/util_test.go
+++ b/services/mgmt/node/impl/util_test.go
@@ -63,11 +63,15 @@
// setupChildCommand configures the child to use the right mounttable root
// and blessings. It returns a cleanup function.
func setupChildCommand(child *blackbox.Child) func() {
+ return setupChildCommandWithBlessing(child, "child")
+}
+
+func setupChildCommandWithBlessing(child *blackbox.Child, blessing string) func() {
cmd := child.Cmd
for i, root := range rt.R().Namespace().Roots() {
cmd.Env = exec.Setenv(cmd.Env, fmt.Sprintf("NAMESPACE_ROOT%d", i), root)
}
- childcreds := security.NewVeyronCredentials(rt.R().Principal(), "child")
+ childcreds := security.NewVeyronCredentials(rt.R().Principal(), blessing)
cmd.Env = exec.Setenv(cmd.Env, "VEYRON_CREDENTIALS", childcreds)
return func() {
os.RemoveAll(childcreds)