veyron/services/mgmt/node/impl: Resume detecta changed associations.
The node manager uses the setuid helper to run application instances with
an associated system name. Resuming an application under a different system
name may result in application failure when a resumed application discovers
that it can no longer access its previously created filesystem state. This
change detects this case and refuses to Resume applications under this
circumstance.
Change-Id: Ic0f317455460456c8f3faf57386fcfb932a36513
diff --git a/services/mgmt/node/impl/app_invoker.go b/services/mgmt/node/impl/app_invoker.go
index 1171507..e26b2bb 100644
--- a/services/mgmt/node/impl/app_invoker.go
+++ b/services/mgmt/node/impl/app_invoker.go
@@ -26,6 +26,7 @@
// info - app manager name and process id for the instance (if running)
// version - symbolic link to installation version for the instance
// <status> - one of the values for instanceState enum
+// systemname - the system name used to execute this instance
// instance-<id b>
// ...
// installation-<id 2>
@@ -404,7 +405,12 @@
// and is probably not a good fit in other contexts. Revisit the design
// as appropriate. This function also internalizes a decision as to when
// it is possible to start an application that needs to be made explicit.
-func systemAccountForHelper(helperStat os.FileInfo, identityNames []string, uat BlessingSystemAssociationStore) (systemName string, err error) {
+func systemAccountForHelper(helperPath string, identityNames []string, uat BlessingSystemAssociationStore) (systemName string, err error) {
+ helperStat, err := os.Stat(helperPath)
+ if err != nil {
+ vlog.Errorf("Stat(%v) failed: %v. helper is required.", helperPath, err)
+ return "", errOperationFailed
+ }
haveHelper := isSetuid(helperStat)
systemName, present := uat.SystemAccountForBlessings(identityNames)
@@ -434,7 +440,7 @@
// TODO(rjkroege): Turning on the setuid feature of the suidhelper
// requires an installer with root permissions to install it in <config.Root>/helper
-func genCmd(instanceDir string, helperPath string, uat BlessingSystemAssociationStore, identityNames []string) (*exec.Cmd, error) {
+func genCmd(instanceDir, helperPath, systemName string) (*exec.Cmd, error) {
versionLink := filepath.Join(instanceDir, "version")
versionDir, err := filepath.EvalSymlinks(versionLink)
if err != nil {
@@ -451,19 +457,8 @@
return nil, errOperationFailed
}
- helperStat, err := os.Stat(helperPath)
- if err != nil {
- vlog.Errorf("Stat(%v) failed: %v. helper is required.", helperPath, err)
- return nil, errOperationFailed
- }
cmd := exec.Command(helperPath)
-
- cmd.Args = append(cmd.Args, "--username")
- uname, err := systemAccountForHelper(helperStat, identityNames, uat)
- if err != nil {
- return nil, err
- }
- cmd.Args = append(cmd.Args, uname)
+ cmd.Args = append(cmd.Args, "--username", systemName)
// TODO(caprita): Also pass in configuration info like NAMESPACE_ROOT to
// the app (to point to the device mounttable).
@@ -473,8 +468,7 @@
return nil, err
}
cmd.Dir = rootDir
- cmd.Args = append(cmd.Args, "--workspace")
- cmd.Args = append(cmd.Args, rootDir)
+ cmd.Args = append(cmd.Args, "--workspace", rootDir)
logDir := filepath.Join(instanceDir, "logs")
if err := mkdir(logDir); err != nil {
@@ -485,18 +479,14 @@
if cmd.Stdout, err = openWriteFile(stdoutLog); err != nil {
return nil, err
}
- cmd.Args = append(cmd.Args, "--stdoutlog")
- cmd.Args = append(cmd.Args, stdoutLog)
+ cmd.Args = append(cmd.Args, "--stdoutlog", stdoutLog)
stderrLog := filepath.Join(logDir, fmt.Sprintf("STDERR-%d", timestamp))
if cmd.Stderr, err = openWriteFile(stderrLog); err != nil {
return nil, err
}
- cmd.Args = append(cmd.Args, "--stderrlog")
- cmd.Args = append(cmd.Args, stderrLog)
-
- cmd.Args = append(cmd.Args, "--run")
- cmd.Args = append(cmd.Args, binPath)
+ cmd.Args = append(cmd.Args, "--stderrlog", stderrLog)
+ cmd.Args = append(cmd.Args, "--run", binPath)
cmd.Args = append(cmd.Args, "--")
// Set up args and env.
@@ -548,11 +538,12 @@
return nil
}
-func (i *appInvoker) run(instanceDir string, blessings []string) error {
+func (i *appInvoker) run(instanceDir, systemName, helper string) error {
if err := transitionInstance(instanceDir, suspended, starting); err != nil {
return err
}
- cmd, err := genCmd(instanceDir, filepath.Join(i.config.Root, "helper"), i.uat, blessings)
+
+ cmd, err := genCmd(instanceDir, helper, systemName)
if err == nil {
err = i.startCmd(instanceDir, cmd)
}
@@ -565,13 +556,27 @@
func (i *appInvoker) Start(call ipc.ServerContext) ([]string, error) {
instanceDir, instanceID, err := i.newInstance()
- if err == nil {
- err = i.run(instanceDir, call.RemoteBlessings().ForContext(call))
- }
if err != nil {
cleanupDir(instanceDir)
return nil, err
}
+
+ helper := filepath.Join(i.config.Root, "helper")
+ systemName, err := systemAccountForHelper(helper, call.RemoteBlessings().ForContext(call), i.uat)
+ if err != nil {
+ cleanupDir(instanceDir)
+ return nil, err
+ }
+
+ if err := saveSystemNameForInstance(instanceDir, systemName); err != nil {
+ cleanupDir(instanceDir)
+ return nil, err
+ }
+
+ if err = i.run(instanceDir, systemName, helper); err != nil {
+ cleanupDir(instanceDir)
+ return nil, err
+ }
return []string{instanceID}, nil
}
@@ -589,13 +594,27 @@
return instanceDir, nil
}
-// TODO(rjkroege): Only the original invoking identity may resume an application.
func (i *appInvoker) Resume(call ipc.ServerContext) error {
instanceDir, err := i.instanceDir()
if err != nil {
return err
}
- return i.run(instanceDir, call.RemoteBlessings().ForContext(call))
+
+ helper := filepath.Join(i.config.Root, "helper")
+ systemName, err := systemAccountForHelper(helper, call.RemoteBlessings().ForContext(call), i.uat)
+ if err != nil {
+ return err
+ }
+
+ startSystemName, err := readSystemNameForInstance(instanceDir)
+ if err != nil {
+ return err
+ }
+
+ if startSystemName != systemName {
+ return verror.NoAccessf("Not allowed to resume an application under a different system name.")
+ }
+ return i.run(instanceDir, systemName, helper)
}
func stopAppRemotely(appVON string) error {
diff --git a/services/mgmt/node/impl/args_darwin_test.go b/services/mgmt/node/impl/args_darwin_test.go
index bf4b58d..e42e887 100644
--- a/services/mgmt/node/impl/args_darwin_test.go
+++ b/services/mgmt/node/impl/args_darwin_test.go
@@ -1,5 +1,6 @@
package impl_test
const (
- testUserName = "_uucp"
+ testUserName = "_uucp"
+ anotherTestUserName = "_lp"
)
diff --git a/services/mgmt/node/impl/args_linux_test.go b/services/mgmt/node/impl/args_linux_test.go
index 07c557f..4b1e0f8 100644
--- a/services/mgmt/node/impl/args_linux_test.go
+++ b/services/mgmt/node/impl/args_linux_test.go
@@ -1,5 +1,6 @@
package impl_test
const (
- testUserName = "uucp"
+ testUserName = "uucp"
+ anotherTestUserName = "lp"
)
diff --git a/services/mgmt/node/impl/associate_instance_test.go b/services/mgmt/node/impl/associate_instance_test.go
new file mode 100644
index 0000000..317c881
--- /dev/null
+++ b/services/mgmt/node/impl/associate_instance_test.go
@@ -0,0 +1,28 @@
+package impl
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+)
+
+func TestSystemNameState(t *testing.T) {
+ dir, err := ioutil.TempDir("", "instance")
+ if err != nil {
+ t.Fatalf("Failed to create temp dir: %v", err)
+ }
+ defer os.RemoveAll(dir)
+
+ expected := "vanadium-user"
+ if err := saveSystemNameForInstance(dir, expected); err != nil {
+ t.Fatalf("saveSystemNameForInstance(%v, %v) failed: %v", dir, expected, err)
+ }
+
+ got, err := readSystemNameForInstance(dir)
+ if err != nil {
+ t.Fatalf("readSystemNameForInstance(%v) failed: ", err)
+ }
+ if got != expected {
+ t.Fatalf("got %v, expected %v", got, expected)
+ }
+}
diff --git a/services/mgmt/node/impl/association_instance.go b/services/mgmt/node/impl/association_instance.go
new file mode 100644
index 0000000..02ae3a7
--- /dev/null
+++ b/services/mgmt/node/impl/association_instance.go
@@ -0,0 +1,30 @@
+package impl
+
+// Code to manage the persistence of which systemName is associated with
+// a given application instance.
+
+import (
+ "io/ioutil"
+ "path/filepath"
+
+ "veyron.io/veyron/veyron2/vlog"
+)
+
+func saveSystemNameForInstance(dir, systemName string) error {
+ snp := filepath.Join(dir, "systemname")
+ if err := ioutil.WriteFile(snp, []byte(systemName), 0600); err != nil {
+ vlog.Errorf("WriteFile(%v, %v) failed: %v", snp, systemName, err)
+ return errOperationFailed
+ }
+ return nil
+}
+
+func readSystemNameForInstance(dir string) (string, error) {
+ snp := filepath.Join(dir, "systemname")
+ name, err := ioutil.ReadFile(snp)
+ if err != nil {
+ vlog.Errorf("ReadFile(%v) failed: %v", snp, err)
+ return "", errOperationFailed
+ }
+ return string(name), nil
+}
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index 9fcee59..baaf81a 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -1285,7 +1285,11 @@
t.Fatalf("AssociateAccount failed %v", err)
}
// Add Start to the ACL list for root/other.
- newACL := security.ACL{In: map[security.BlessingPattern]security.LabelSet{"root/other/...": security.AllLabels}}
+ newACL, _, err := nodeStub.GetACL(selfRT.NewContext())
+ if err != nil {
+ t.Fatalf("GetACL failed %v", err)
+ }
+ newACL.In["root/other/..."] = security.AllLabels
if err = nodeStub.SetACL(selfRT.NewContext(), newACL, ""); err != nil {
t.Fatalf("SetACL failed %v", err)
}
@@ -1293,5 +1297,28 @@
vlog.VI(2).Infof("other attempting to run an app with access. Should succeed.")
instance2ID := startApp(t, appID, otherRT)
verifyHelperArgs(t, <-pingCh, testUserName) // Wait until the app pings us that it's ready.
+ suspendApp(t, appID, instance2ID, otherRT)
+
+ vlog.VI(2).Infof("Verify that Resume with the same systemName works.")
+ resumeApp(t, appID, instance2ID, otherRT)
+ verifyHelperArgs(t, <-pingCh, testUserName) // Wait until the app pings us that it's ready.
+ suspendApp(t, appID, instance2ID, otherRT)
+
+ // Change the associated system name.
+ if err = nodeStub.AssociateAccount(selfRT.NewContext(), []string{"root/other"}, anotherTestUserName); err != nil {
+ t.Fatalf("AssociateAccount failed %v", err)
+ }
+
+ vlog.VI(2).Infof("Show that Resume with a different systemName fails.")
+ resumeAppExpectError(t, appID, instance2ID, verror.NoAccess, otherRT)
+
+ // Clean up.
stopApp(t, appID, instance2ID, otherRT)
+
+ vlog.VI(2).Infof("Show that Start with different systemName works.")
+ instance3ID := startApp(t, appID, otherRT)
+ verifyHelperArgs(t, <-pingCh, anotherTestUserName) // Wait until the app pings us that it's ready.
+
+ // Clean up.
+ stopApp(t, appID, instance3ID, otherRT)
}
diff --git a/services/mgmt/node/impl/util_test.go b/services/mgmt/node/impl/util_test.go
index fd543dd..8c93dba 100644
--- a/services/mgmt/node/impl/util_test.go
+++ b/services/mgmt/node/impl/util_test.go
@@ -252,6 +252,12 @@
}
}
+func resumeAppExpectError(t *testing.T, appID, instanceID string, expectedError verror.ID, opt ...veyron2.Runtime) {
+ if err := appStub(t, appID, instanceID).Resume(ort(opt).NewContext()); err == nil || !verror.Is(err, expectedError) {
+ t.Fatalf("Resume(%v/%v) expected to fail with %v, got %v instead", appID, instanceID, expectedError, err)
+ }
+}
+
func updateApp(t *testing.T, appID string, opt ...veyron2.Runtime) {
if err := appStub(t, appID).Update(ort(opt).NewContext()); err != nil {
t.Fatalf("Update(%v) failed: %v", appID, err)