Merge "veyron/services/mgmt/node/impl: identity management for child apps (agent-less)."
diff --git a/runtimes/google/rt/mgmt.go b/runtimes/google/rt/mgmt.go
index 33dc8c2..0438791 100644
--- a/runtimes/google/rt/mgmt.go
+++ b/runtimes/google/rt/mgmt.go
@@ -10,6 +10,7 @@
"veyron.io/veyron/veyron2/ipc"
"veyron.io/veyron/veyron2/mgmt"
"veyron.io/veyron/veyron2/naming"
+ "veyron.io/veyron/veyron2/options"
"veyron.io/veyron/veyron/lib/exec"
"veyron.io/veyron/veyron/runtimes/google/appcycle"
@@ -60,8 +61,16 @@
if err != nil {
return err
}
+ var serverOpts []ipc.ServerOpt
+ parentPeerPattern, err := handle.Config.Get(mgmt.ParentBlessingConfigKey)
+ if err == nil && parentPeerPattern != "" {
+ // Grab the blessing from our blessing store that the parent
+ // told us to use so they can talk to us.
+ serverBlessing := rt.Principal().BlessingStore().ForPeer(parentPeerPattern)
+ serverOpts = append(serverOpts, options.ServerBlessings{serverBlessing})
+ }
m.rt = rt
- m.server, err = rt.NewServer()
+ m.server, err = rt.NewServer(serverOpts...)
if err != nil {
return err
}
diff --git a/services/mgmt/node/impl/app_invoker.go b/services/mgmt/node/impl/app_invoker.go
index 6ff19f7..55882ae 100644
--- a/services/mgmt/node/impl/app_invoker.go
+++ b/services/mgmt/node/impl/app_invoker.go
@@ -20,9 +20,10 @@
// current - symbolic link to the current version
// instances/
// instance-<id a>/ - instances are labelled with ids
+// credentials/ - holds veyron credentials
// root/ - workspace that the instance is run from
// logs/ - stderr/stdout and log files generated by instance
-// info - app manager name and process id for the instance (if running)
+// info - metadata for the instance (such as app cycle manager name and process id)
// 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
@@ -85,8 +86,10 @@
import (
"crypto/md5"
+ "crypto/rand"
"encoding/base64"
"encoding/binary"
+ "encoding/hex"
"encoding/json"
"fmt"
"hash/crc64"
@@ -104,6 +107,7 @@
"veyron.io/veyron/veyron2/mgmt"
"veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/rt"
+ "veyron.io/veyron/veyron2/security"
"veyron.io/veyron/veyron2/services/mgmt/appcycle"
"veyron.io/veyron/veyron2/services/mgmt/application"
"veyron.io/veyron/veyron2/services/mounttable"
@@ -112,14 +116,17 @@
"veyron.io/veyron/veyron2/vlog"
vexec "veyron.io/veyron/veyron/lib/exec"
+ "veyron.io/veyron/veyron/lib/flags/consts"
"veyron.io/veyron/veyron/lib/glob"
+ vsecurity "veyron.io/veyron/veyron/security"
iconfig "veyron.io/veyron/veyron/services/mgmt/node/config"
)
// instanceInfo holds state about a running instance.
type instanceInfo struct {
- AppCycleMgrName string
- Pid int
+ AppCycleMgrName string
+ Pid int
+ NodeManagerPeerPattern string
}
func saveInstanceInfo(dir string, info *instanceInfo) error {
@@ -217,6 +224,16 @@
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
}
+// generateRandomString returns a cryptographically-strong random string.
+func generateRandomString() (string, error) {
+ b := make([]byte, 16)
+ _, err := rand.Read(b)
+ if err != nil {
+ return "", err
+ }
+ return hex.EncodeToString(b), nil
+}
+
// TODO(caprita): Nothing prevents different applications from sharing the same
// title, and thereby being installed in the same app dir. Do we want to
// prevent that for the same user or across users?
@@ -361,8 +378,84 @@
return installationDir, nil
}
+// setupIdentity sets up the instance's principal, with the right blessings.
+func setupIdentity(instanceDir, versionDir string, call ipc.ServerContext, info *instanceInfo) error {
+ credentialsDir := filepath.Join(instanceDir, "credentials")
+ // TODO(caprita): The app's system user id needs access to this dir.
+ // Use the suidhelper to chown it.
+ p, err := vsecurity.CreatePersistentPrincipal(credentialsDir, nil)
+ if err != nil {
+ vlog.Errorf("CreatePersistentPrincipal(%v, nil) failed: %v", credentialsDir, err)
+ return errOperationFailed
+ }
+ // Read the app installation version's envelope to obtain the app title.
+ //
+ // NOTE: we could have gotten this from the suffix as well, but the
+ // format of the object name suffix may change in the future: there's no
+ // guarantee it will always include the title.
+ envelope, err := loadEnvelope(versionDir)
+ if err != nil {
+ return err
+ }
+ nmPrincipal := call.LocalPrincipal()
+ // Take the blessings conferred upon us by the Start-er, extend them
+ // with the app title.
+ // TODO(caprita): Revisit UnconstrainedUse.
+ appBlessings, err := nmPrincipal.Bless(p.PublicKey(), call.Blessings(), envelope.Title, security.UnconstrainedUse())
+ if err != nil {
+ vlog.Errorf("Bless() failed: %v", err)
+ return errOperationFailed
+ }
+ // The blessings we extended from the blessings that the Start-er
+ // granted are the default blessings for the app.
+ if err := p.BlessingStore().SetDefault(appBlessings); err != nil {
+ vlog.Errorf("BlessingStore.SetDefault() failed: %v", err)
+ return errOperationFailed
+ }
+ if _, err := p.BlessingStore().Set(appBlessings, security.AllPrincipals); err != nil {
+ vlog.Errorf("BlessingStore.Set() failed: %v", err)
+ return errOperationFailed
+ }
+ if err := p.AddToRoots(appBlessings); err != nil {
+ vlog.Errorf("AddToRoots() failed: %v", err)
+ return errOperationFailed
+ }
+ // In addition, we give the app separate blessings for the purpose of
+ // communicating with the node manager.
+ nmBlessings, err := nmPrincipal.Bless(p.PublicKey(), nmPrincipal.BlessingStore().Default(), "callback", security.UnconstrainedUse())
+ // Put the names of the node manager's default blessings as patterns for
+ // the child, so that the child uses the right blessing when talking
+ // back to the node manager.
+ names := nmPrincipal.BlessingStore().Default().ForContext(call)
+ for _, n := range names {
+ if _, err := p.BlessingStore().Set(nmBlessings, security.BlessingPattern(n)); err != nil {
+ vlog.Errorf("BlessingStore.Set() failed: %v", err)
+ return errOperationFailed
+ }
+ }
+ // We also want to override the app cycle manager's server blessing in
+ // the child (so that the node manager can send RPCs to it). We signal
+ // to the child's app manager to use a randomly generated pattern to
+ // extract the right blessing to use from its store for this purpose.
+ randomPattern, err := generateRandomString()
+ if err != nil {
+ vlog.Errorf("generateRandomString() failed: %v", err)
+ return errOperationFailed
+ }
+ if _, err := p.BlessingStore().Set(nmBlessings, security.BlessingPattern(randomPattern)); err != nil {
+ vlog.Errorf("BlessingStore.Set() failed: %v", err)
+ return errOperationFailed
+ }
+ info.NodeManagerPeerPattern = randomPattern
+ if err := p.AddToRoots(nmBlessings); err != nil {
+ vlog.Errorf("AddToRoots() failed: %v", err)
+ return errOperationFailed
+ }
+ return nil
+}
+
// newInstance sets up the directory for a new application instance.
-func (i *appInvoker) newInstance() (string, string, error) {
+func (i *appInvoker) newInstance(call ipc.ServerContext) (string, string, error) {
installationDir, err := i.installationDir()
if err != nil {
return "", "", err
@@ -386,6 +479,13 @@
vlog.Errorf("Symlink(%v, %v) failed: %v", versionDir, versionLink, err)
return instanceDir, instanceID, errOperationFailed
}
+ instanceInfo := new(instanceInfo)
+ if err := setupIdentity(instanceDir, versionDir, call, instanceInfo); err != nil {
+ return instanceDir, instanceID, err
+ }
+ if err := saveInstanceInfo(instanceDir, instanceInfo); err != nil {
+ return instanceDir, instanceID, err
+ }
if err := initializeInstance(instanceDir, suspended); err != nil {
return instanceDir, instanceID, err
}
@@ -440,8 +540,6 @@
return "", errOperationFailed
}
-// 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, helperPath, systemName string) (*exec.Cmd, error) {
versionLink := filepath.Join(instanceDir, "version")
versionDir, err := filepath.EvalSymlinks(versionLink)
@@ -465,6 +563,7 @@
// TODO(caprita): Also pass in configuration info like NAMESPACE_ROOT to
// the app (to point to the device mounttable).
cmd.Env = envelope.Env
+ cmd.Env = append(cmd.Env, consts.VeyronCredentials+"="+filepath.Join(instanceDir, "credentials"))
rootDir := filepath.Join(instanceDir, "root")
if err := mkdir(rootDir); err != nil {
return nil, err
@@ -497,6 +596,10 @@
}
func (i *appInvoker) startCmd(instanceDir string, cmd *exec.Cmd) error {
+ info, err := loadInstanceInfo(instanceDir)
+ if err != nil {
+ return err
+ }
// Setup up the child process callback.
callbackState := i.callback
listener := callbackState.listenFor(mgmt.AppCycleManagerConfigKey)
@@ -505,6 +608,7 @@
cfg.Set(mgmt.ParentNameConfigKey, listener.name())
cfg.Set(mgmt.ProtocolConfigKey, "tcp")
cfg.Set(mgmt.AddressConfigKey, "127.0.0.1:0")
+ cfg.Set(mgmt.ParentBlessingConfigKey, info.NodeManagerPeerPattern)
handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg})
defer func() {
if handle != nil {
@@ -528,11 +632,8 @@
if err != nil {
return errOperationFailed
}
- instanceInfo := &instanceInfo{
- AppCycleMgrName: childName,
- Pid: handle.Pid(),
- }
- if err := saveInstanceInfo(instanceDir, instanceInfo); err != nil {
+ info.AppCycleMgrName, info.Pid = childName, handle.Pid()
+ if err := saveInstanceInfo(instanceDir, info); err != nil {
return err
}
// TODO(caprita): Spin up a goroutine to reap child status upon exit and
@@ -559,7 +660,7 @@
func (i *appInvoker) Start(call ipc.ServerContext) ([]string, error) {
helper := i.config.Helper
- instanceDir, instanceID, err := i.newInstance()
+ instanceDir, instanceID, err := i.newInstance(call)
if err != nil {
cleanupDir(instanceDir, helper)
return nil, err
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index 8f44059..9b1f878 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -312,7 +312,7 @@
// convenient to put it there so we have everything in one place.
currLink := filepath.Join(root, "current_link")
- crDir, crEnv := credentialsForChild("anyvalue")
+ crDir, crEnv := credentialsForChild("nodemanager")
defer os.RemoveAll(crDir)
nmArgs := []string{"factoryNM", root, "unused_helper", mockApplicationRepoName, currLink}
args, env := sh.CommandEnvelope(nodeManagerCmd, crEnv, nmArgs...)
@@ -350,7 +350,7 @@
// Set up a second version of the node manager. The information in the
// envelope will be used by the node manager to stage the next version.
- crDir, crEnv = credentialsForChild("anyvalue")
+ crDir, crEnv = credentialsForChild("nodemanager")
defer os.RemoveAll(crDir)
*envelope = envelopeFromShell(sh, crEnv, nodeManagerCmd, application.NodeManagerTitle, "v2NM")
updateNode(t, "factoryNM")
@@ -398,7 +398,7 @@
}
// Create a third version of the node manager and issue an update.
- crDir, crEnv = credentialsForChild("anyvalue")
+ crDir, crEnv = credentialsForChild("nodemanager")
defer os.RemoveAll(crDir)
*envelope = envelopeFromShell(sh, crEnv, nodeManagerCmd, application.NodeManagerTitle,
"v3NM")
@@ -462,9 +462,9 @@
nms.ExpectEOF()
}
-type pingServerDisp chan<- string
+type pingServer chan<- string
-func (p pingServerDisp) Ping(_ ipc.ServerCall, arg string) {
+func (p pingServer) Ping(_ ipc.ServerCall, arg string) {
p <- arg
}
@@ -474,7 +474,7 @@
func setupPingServer(t *testing.T) (<-chan string, func()) {
server, _ := newServer()
pingCh := make(chan string, 1)
- if err := server.Serve("pingserver", pingServerDisp(pingCh), nil); err != nil {
+ if err := server.Serve("pingserver", pingServer(pingCh), nil); err != nil {
t.Fatalf("Serve(%q, <dispatcher>) failed: %v", "pingserver", err)
}
return pingCh, func() {
@@ -546,7 +546,7 @@
// Create a script wrapping the test target that implements suidhelper.
helperPath := generateSuidHelperScript(t, root)
- crDir, crEnv := credentialsForChild("anyvalue")
+ crDir, crEnv := credentialsForChild("nodemanager")
defer os.RemoveAll(crDir)
// Set up the node manager. Since we won't do node manager updates,
@@ -560,10 +560,8 @@
resolve(t, "pingserver", 1)
- crDir, crEnv = credentialsForChild("anyvalue")
- defer os.RemoveAll(crDir)
// Create an envelope for a first version of the app.
- *envelope = envelopeFromShell(sh, crEnv, appCmd, "google naps", "appV1")
+ *envelope = envelopeFromShell(sh, nil, appCmd, "google naps", "appV1")
// Install the app.
appID := installApp(t)
@@ -623,9 +621,7 @@
updateAppExpectError(t, appID, verror.BadArg)
// Create a second version of the app and update the app to it.
- crDir, crEnv = credentialsForChild("anyvalue")
- defer os.RemoveAll(crDir)
- *envelope = envelopeFromShell(sh, crEnv, appCmd, "google naps", "appV2")
+ *envelope = envelopeFromShell(sh, nil, appCmd, "google naps", "appV2")
updateApp(t, appID)
@@ -704,16 +700,6 @@
nms.ExpectEOF()
}
-type granter struct {
- ipc.CallOpt
- p security.Principal
- extension string
-}
-
-func (g *granter) Grant(other security.Blessings) (security.Blessings, error) {
- return g.p.Bless(other.PublicKey(), g.p.BlessingStore().Default(), g.extension, security.UnconstrainedUse())
-}
-
func newRuntime(t *testing.T) veyron2.Runtime {
runtime, err := rt.New()
if err != nil {
@@ -744,7 +730,7 @@
root, cleanup := setupRootDir(t)
defer cleanup()
- crDir, crEnv := credentialsForChild("anyvalue")
+ crDir, crEnv := credentialsForChild("nodemanager")
defer os.RemoveAll(crDir)
// Create a script wrapping the test target that implements suidhelper.
@@ -756,9 +742,7 @@
pid := readPID(t, nms)
defer syscall.Kill(pid, syscall.SIGINT)
- crDir, crEnv = credentialsForChild("mydevice/anyvalue")
- defer os.RemoveAll(crDir)
- *envelope = envelopeFromShell(sh, crEnv, appCmd, "google naps", "trapp")
+ *envelope = envelopeFromShell(sh, nil, appCmd, "google naps", "trapp")
nodeStub := node.NodeClient("nm//nm")
selfRT := rt.R()
@@ -831,7 +815,7 @@
t.Fatal(err)
}
- crDir, crEnv := credentialsForChild("anyvalue")
+ crDir, crEnv := credentialsForChild("nodemanager")
defer os.RemoveAll(crDir)
// Set up the node manager. Since we won't do node manager updates,
@@ -841,9 +825,7 @@
defer syscall.Kill(pid, syscall.SIGINT)
// Create an envelope for an app.
- crDir, crEnv = credentialsForChild("anyvalue")
- defer os.RemoveAll(crDir)
- *envelope = envelopeFromShell(sh, crEnv, appCmd, "google naps")
+ *envelope = envelopeFromShell(sh, nil, appCmd, "google naps")
nodeStub := node.NodeClient("nm//nm")
acl, etag, err := nodeStub.GetACL(selfRT.NewContext())
@@ -959,7 +941,7 @@
root, cleanup := setupRootDir(t)
defer cleanup()
- crDir, crEnv := credentialsForChild("anyvalue")
+ crDir, crEnv := credentialsForChild("nodemanager")
defer os.RemoveAll(crDir)
// Create a script wrapping the test target that implements suidhelper.
@@ -976,7 +958,7 @@
defer cleanup()
// Create the envelope for the first version of the app.
- *envelope = envelopeFromShell(sh, crEnv, appCmd, "google naps", "appV1")
+ *envelope = envelopeFromShell(sh, nil, appCmd, "google naps", "appV1")
// Install the app.
appID := installApp(t)
@@ -1153,7 +1135,7 @@
if err := idp.Bless(otherRT.Principal(), "other"); err != nil {
t.Fatal(err)
}
- crFile, crEnv := credentialsForChild("anyvalue")
+ crFile, crEnv := credentialsForChild("nodemanager")
defer os.RemoveAll(crFile)
_, nms := runShellCommand(t, sh, crEnv, nodeManagerCmd, "nm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
@@ -1259,7 +1241,7 @@
t.Fatal(err)
}
- crDir, crEnv := credentialsForChild("anyvalue")
+ crDir, crEnv := credentialsForChild("nodemanager")
defer os.RemoveAll(crDir)
// Create a script wrapping the test target that implements
@@ -1277,15 +1259,12 @@
server, _ := newServer()
defer server.Stop()
pingCh := make(chan string, 1)
- if err := server.Serve("pingserver", pingServerDisp(pingCh), nil); err != nil {
+ if err := server.Serve("pingserver", pingServer(pingCh), nil); err != nil {
t.Fatalf("Serve(%q, <dispatcher>) failed: %v", "pingserver", err)
}
- // Create an envelope for a first version of the app with an
- // appropriate blessing.
- crDir, crEnv = credentialsForChild("alice/child")
- defer os.RemoveAll(crDir)
- *envelope = envelopeFromShell(sh, crEnv, appCmd, "google naps", "appV1")
+ // Create an envelope for a first version of the app.
+ *envelope = envelopeFromShell(sh, nil, appCmd, "google naps", "appV1")
// Install and start the app as root/self.
appID := installApp(t, selfRT)
diff --git a/services/mgmt/node/impl/util_test.go b/services/mgmt/node/impl/util_test.go
index 00762a5..b9b9ad3 100644
--- a/services/mgmt/node/impl/util_test.go
+++ b/services/mgmt/node/impl/util_test.go
@@ -15,6 +15,7 @@
"veyron.io/veyron/veyron2/ipc"
"veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/rt"
+ "veyron.io/veyron/veyron2/security"
"veyron.io/veyron/veyron2/services/mgmt/node"
"veyron.io/veyron/veyron2/verror"
"veyron.io/veyron/veyron2/vlog"
@@ -23,7 +24,7 @@
"veyron.io/veyron/veyron/lib/flags/consts"
"veyron.io/veyron/veyron/lib/modules"
"veyron.io/veyron/veyron/lib/modules/core"
- "veyron.io/veyron/veyron/lib/testutil/security"
+ tsecurity "veyron.io/veyron/veyron/lib/testutil/security"
"veyron.io/veyron/veyron/profiles/static"
"veyron.io/veyron/veyron/services/mgmt/node/impl"
"veyron.io/veyron/veyron2/services/mgmt/application"
@@ -53,7 +54,7 @@
}
func credentialsForChild(blessing string) (string, []string) {
- creds := security.NewVeyronCredentials(rt.R().Principal(), blessing)
+ creds := tsecurity.NewVeyronCredentials(rt.R().Principal(), blessing)
return creds, []string{consts.VeyronCredentials + "=" + creds}
}
@@ -229,8 +230,18 @@
return appID
}
+type granter struct {
+ ipc.CallOpt
+ p security.Principal
+ extension string
+}
+
+func (g *granter) Grant(other security.Blessings) (security.Blessings, error) {
+ return g.p.Bless(other.PublicKey(), g.p.BlessingStore().Default(), g.extension, security.UnconstrainedUse())
+}
+
func startAppImpl(t *testing.T, appID string, opt []veyron2.Runtime) (string, error) {
- if instanceIDs, err := appStub(appID).Start(ort(opt).NewContext()); err != nil {
+ if instanceIDs, err := appStub(appID).Start(ort(opt).NewContext(), &granter{p: ort(opt).Principal(), extension: "forapp"}); err != nil {
return "", err
} else {
if want, got := 1, len(instanceIDs); want != got {