Merge "veyron/tools/mgmt/nminstall: implement fetching binaries from local path."
diff --git a/lib/exec/parent.go b/lib/exec/parent.go
index a3aebec..b0d995d 100644
--- a/lib/exec/parent.go
+++ b/lib/exec/parent.go
@@ -66,7 +66,6 @@
 // NewParentHandle creates a ParentHandle for the child process represented by
 // an instance of exec.Cmd.
 func NewParentHandle(c *exec.Cmd, opts ...ParentHandleOpt) *ParentHandle {
-
 	cfg, secret := NewConfig(), ""
 	tk := timekeeper.RealTime()
 	for _, opt := range opts {
@@ -92,7 +91,18 @@
 // Start starts the child process, sharing a secret with it and
 // setting up a communication channel over which to read its status.
 func (p *ParentHandle) Start() error {
-	p.c.Env = append(p.c.Env, VersionVariable+"="+version1)
+	// Make sure that there are no instances of the VersionVariable
+	// already in the environment (which can happen when a subprocess
+	// creates a subprocess etc)
+	nenv := make([]string, 0, len(p.c.Env)+1)
+	for _, e := range p.c.Env {
+		if strings.HasPrefix(e, VersionVariable+"=") {
+			continue
+		}
+		nenv = append(nenv, e)
+	}
+	p.c.Env = append(nenv, VersionVariable+"="+version1)
+
 	// Create anonymous pipe for communicating data between the child
 	// and the parent.
 	dataRead, dataWrite, err := os.Pipe()
diff --git a/lib/flags/flags.go b/lib/flags/flags.go
index f6c2ef1..102ae14 100644
--- a/lib/flags/flags.go
+++ b/lib/flags/flags.go
@@ -11,11 +11,11 @@
 type FlagGroup int
 
 const (
-	// Essential identifies the flags and associated environment variables
-	// required by all Vanadium processes. Namely:
+	// Runtime identifies the flags and associated environment variables
+	// used by the Vanadium process runtime. Namely:
 	// --veyron.namespace.root (which may be repeated to supply multiple values)
 	// --veyron.credentials
-	Essential FlagGroup = iota
+	Runtime FlagGroup = iota
 	// Listen identifies the flags typically required to configure
 	// ipc.ListenSpec. Namely:
 	// --veyron.tcp.protocol
@@ -44,8 +44,8 @@
 	return nil
 }
 
-// EssentialFlags contains the values of the Essential flag group.
-type EssentialFlags struct {
+// RuntimeFlags contains the values of the Runtime flag group.
+type RuntimeFlags struct {
 	// NamespaceRoots may be initialized by NAMESPACE_ROOT* enivornment
 	// variables as well as --veyron.namespace.root. The command line
 	// will override the environment.
@@ -65,10 +65,10 @@
 	ListenProxy    string
 }
 
-// createAndRegisterEssentialFlags creates and registers the EssentialFlags
+// createAndRegisterRuntimeFlags creates and registers the RuntimeFlags
 // group with the supplied flag.FlagSet.
-func createAndRegisterEssentialFlags(fs *flag.FlagSet) *EssentialFlags {
-	f := &EssentialFlags{}
+func createAndRegisterRuntimeFlags(fs *flag.FlagSet) *RuntimeFlags {
+	f := &RuntimeFlags{}
 	fs.Var(&f.namespaceRootsFlag, "veyron.namespace.root", "local namespace root; can be repeated to provided multiple roots")
 	fs.StringVar(&f.Credentials, "veyron.credentials", "", "directory to use for storing security credentials")
 	return f
@@ -88,14 +88,16 @@
 
 // CreateAndRegister creates a new set of flag groups as specified by the
 // supplied flag group parameters and registers them with the supplied
-// flag.Flagset. The Essential flag group is always included.
+// flag.Flagset.
 func CreateAndRegister(fs *flag.FlagSet, groups ...FlagGroup) *Flags {
+	if len(groups) == 0 {
+		return nil
+	}
 	f := &Flags{FlagSet: fs, groups: make(map[FlagGroup]interface{})}
-	f.groups[Essential] = createAndRegisterEssentialFlags(fs)
-	for _, s := range groups {
-		switch s {
-		case Essential:
-			// do nothing, always included
+	for _, g := range groups {
+		switch g {
+		case Runtime:
+			f.groups[Runtime] = createAndRegisterRuntimeFlags(fs)
 		case Listen:
 			f.groups[Listen] = createAndRegisterListenFlags(fs)
 		}
@@ -103,10 +105,13 @@
 	return f
 }
 
-// EssentialFlags returns the Essential flag subset stored in its Flags
+// RuntimeFlags returns the Runtime flag subset stored in its Flags
 // instance.
-func (f *Flags) EssentialFlags() EssentialFlags {
-	from := f.groups[Essential].(*EssentialFlags)
+func (f *Flags) RuntimeFlags() RuntimeFlags {
+	if p := f.groups[Runtime]; p == nil {
+		return RuntimeFlags{}
+	}
+	from := f.groups[Runtime].(*RuntimeFlags)
 	to := *from
 	to.NamespaceRoots = make([]string, len(from.NamespaceRoots))
 	copy(to.NamespaceRoots, from.NamespaceRoots)
@@ -138,7 +143,7 @@
 
 // legacyEnvInit provides support for the legacy NAMESPACE_ROOT? and
 // VEYRON_CREDENTIALS env vars.
-func (es *EssentialFlags) legacyEnvInit() {
+func (es *RuntimeFlags) legacyEnvInit() {
 	for _, ev := range os.Environ() {
 		p := strings.SplitN(ev, "=", 2)
 		if len(p) != 2 {
@@ -156,15 +161,22 @@
 
 // Parse parses the supplied args, as per flag.Parse
 func (f *Flags) Parse(args []string) error {
-	f.groups[Essential].(*EssentialFlags).legacyEnvInit()
+	hasrt := f.groups[Runtime] != nil
+	if hasrt {
+		f.groups[Runtime].(*RuntimeFlags).legacyEnvInit()
+	}
+
 	// TODO(cnicolaou): implement a single env var 'VANADIUM_OPTS'
 	// that can be used to specify any command line.
 	if err := f.FlagSet.Parse(args); err != nil {
 		return err
 	}
-	essential := f.groups[Essential].(*EssentialFlags)
-	if len(essential.namespaceRootsFlag.roots) > 0 {
-		essential.NamespaceRoots = essential.namespaceRootsFlag.roots
+
+	if hasrt {
+		runtime := f.groups[Runtime].(*RuntimeFlags)
+		if len(runtime.namespaceRootsFlag.roots) > 0 {
+			runtime.NamespaceRoots = runtime.namespaceRootsFlag.roots
+		}
 	}
 	return nil
 }
diff --git a/lib/flags/flags_test.go b/lib/flags/flags_test.go
index e8ca273..8d10c8a 100644
--- a/lib/flags/flags_test.go
+++ b/lib/flags/flags_test.go
@@ -11,25 +11,32 @@
 )
 
 func TestFlags(t *testing.T) {
-	fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError))
+	fs := flag.NewFlagSet("test", flag.ContinueOnError)
+	if flags.CreateAndRegister(fs) != nil {
+		t.Errorf("should have failed")
+	}
+	fl := flags.CreateAndRegister(fs, flags.Runtime)
+	if fl == nil {
+		t.Fatalf("should have returned a non-nil value")
+	}
 	creds := "creddir"
 	roots := []string{"ab:cd:ef"}
 	args := []string{"--veyron.credentials=" + creds, "--veyron.namespace.root=" + roots[0]}
 	fl.Parse(args)
-	es := fl.EssentialFlags()
-	if got, want := es.NamespaceRoots, roots; !reflect.DeepEqual(got, want) {
+	rtf := fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, roots; !reflect.DeepEqual(got, want) {
 		t.Errorf("got %v, want %v", got, want)
 	}
-	if got, want := es.Credentials, creds; !reflect.DeepEqual(got, want) {
+	if got, want := rtf.Credentials, creds; !reflect.DeepEqual(got, want) {
 		t.Errorf("got %v, want %v", got, want)
 	}
 	if got, want := fl.HasGroup(flags.Listen), false; got != want {
 		t.Errorf("got %t, want %t", got, want)
 	}
 	// Make sure we have a deep copy.
-	es.NamespaceRoots[0] = "oooh"
-	es = fl.EssentialFlags()
-	if got, want := es.NamespaceRoots, roots; !reflect.DeepEqual(got, want) {
+	rtf.NamespaceRoots[0] = "oooh"
+	rtf = fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, roots; !reflect.DeepEqual(got, want) {
 		t.Errorf("got %v, want %v", got, want)
 	}
 }
@@ -37,7 +44,7 @@
 func TestFlagError(t *testing.T) {
 	fs := flag.NewFlagSet("test", flag.ContinueOnError)
 	fs.SetOutput(ioutil.Discard)
-	fl := flags.CreateAndRegister(fs)
+	fl := flags.CreateAndRegister(fs, flags.Runtime)
 	addr := "192.168.10.1:0"
 	args := []string{"--xxxveyron.tcp.address=" + addr, "not an arg"}
 	err := fl.Parse(args)
@@ -50,7 +57,7 @@
 }
 
 func TestFlagsGroups(t *testing.T) {
-	fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Listen)
+	fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Runtime, flags.Listen)
 	if got, want := fl.HasGroup(flags.Listen), true; got != want {
 		t.Errorf("got %t, want %t", got, want)
 	}
@@ -59,7 +66,7 @@
 	args := []string{"--veyron.tcp.address=" + addr, "--veyron.namespace.root=" + roots[0]}
 	fl.Parse(args)
 	lf := fl.ListenFlags()
-	if got, want := fl.EssentialFlags().NamespaceRoots, roots; !reflect.DeepEqual(got, want) {
+	if got, want := fl.RuntimeFlags().NamespaceRoots, roots; !reflect.DeepEqual(got, want) {
 		t.Errorf("got %v, want %v", got, want)
 	}
 	if got, want := lf.ListenAddress.String(), addr; got != want {
@@ -81,39 +88,41 @@
 	defer os.Setenv(rootEnvVar0, oldroot0)
 
 	os.Setenv(credEnvVar, "bar")
-	fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError))
+	fl := flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Runtime)
 	if err := fl.Parse([]string{}); err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
-	es := fl.EssentialFlags()
-	if got, want := es.Credentials, "bar"; got != want {
+	rtf := fl.RuntimeFlags()
+	if got, want := rtf.Credentials, "bar"; got != want {
 		t.Errorf("got %q, want %q", got, want)
 	}
 
 	if err := fl.Parse([]string{"--veyron.credentials=baz"}); err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
-	es = fl.EssentialFlags()
-	if got, want := es.Credentials, "baz"; got != want {
+	rtf = fl.RuntimeFlags()
+	if got, want := rtf.Credentials, "baz"; got != want {
 		t.Errorf("got %q, want %q", got, want)
 	}
 
 	os.Setenv(rootEnvVar, "a:1")
 	os.Setenv(rootEnvVar0, "a:2")
-	fl = flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError))
+	fl = flags.CreateAndRegister(flag.NewFlagSet("test", flag.ContinueOnError), flags.Runtime)
 	if err := fl.Parse([]string{}); err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
-	es = fl.EssentialFlags()
-	if got, want := es.NamespaceRoots, []string{"a:1", "a:2"}; !reflect.DeepEqual(got, want) {
+	rtf = fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, []string{"a:1", "a:2"}; !reflect.DeepEqual(got, want) {
 		t.Errorf("got %q, want %q", got, want)
 	}
-	if err := fl.Parse([]string{"--veyron.namespace.root=b:1", "--veyron.namespace.root=b:2", "--veyron.namespace.root=b:3"}); err != nil {
+	if err := fl.Parse([]string{"--veyron.namespace.root=b:1", "--veyron.namespace.root=b:2", "--veyron.namespace.root=b:3", "--veyron.credentials=b:4"}); err != nil {
 		t.Fatalf("unexpected error: %s", err)
 	}
-	es = fl.EssentialFlags()
-	if got, want := es.NamespaceRoots, []string{"b:1", "b:2", "b:3"}; !reflect.DeepEqual(got, want) {
+	rtf = fl.RuntimeFlags()
+	if got, want := rtf.NamespaceRoots, []string{"b:1", "b:2", "b:3"}; !reflect.DeepEqual(got, want) {
 		t.Errorf("got %q, want %q", got, want)
 	}
-
+	if got, want := rtf.Credentials, "b:4"; got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
 }
diff --git a/lib/modules/exec.go b/lib/modules/exec.go
index e81d38b..26c8b61 100644
--- a/lib/modules/exec.go
+++ b/lib/modules/exec.go
@@ -101,7 +101,16 @@
 	newargs := []string{os.Args[0]}
 	newargs = append(newargs, testFlags()...)
 	newargs = append(newargs, args...)
-	return newargs, append(env, eh.entryPoint)
+	// Be careful to remove any existing ShellEntryPoint env vars. This
+	// can happen when subprocesses run other subprocesses etc.
+	cleaned := make([]string, 0, len(env)+1)
+	for _, e := range env {
+		if strings.HasPrefix(e, ShellEntryPoint+"=") {
+			continue
+		}
+		cleaned = append(cleaned, e)
+	}
+	return newargs, append(cleaned, eh.entryPoint)
 }
 
 func (eh *execHandle) start(sh *Shell, env []string, args ...string) (Handle, error) {
@@ -136,8 +145,8 @@
 	if err := handle.Start(); err != nil {
 		return nil, err
 	}
-	// TODO(cnicolaou): make this timeout configurable
-	err = handle.WaitForReady(time.Minute)
+	vlog.VI(1).Infof("Started: %q, pid %d", eh.name, cmd.Process.Pid)
+	err = handle.WaitForReady(sh.startTimeout)
 	return eh, err
 }
 
@@ -223,11 +232,17 @@
 }
 
 func (child *childRegistrar) dispatch() error {
-	ch, _ := vexec.GetChildHandle()
+	ch, err := vexec.GetChildHandle()
+	if err != nil {
+		// This is for debugging only. It's perfectly reasonable for this
+		// error to occur if the process is started by a means other
+		// than the exec library.
+		vlog.VI(1).Infof("failed to get child handle: %s", err)
+	}
+
 	// Only signal that the child is ready or failed if we successfully get
 	// a child handle. We most likely failed to get a child handle
 	// because the subprocess was run directly from the command line.
-
 	command := os.Getenv(ShellEntryPoint)
 	if len(command) == 0 {
 		err := fmt.Errorf("Failed to find entrypoint %q", ShellEntryPoint)
diff --git a/lib/modules/shell.go b/lib/modules/shell.go
index 6919bd3..1bf3e61 100644
--- a/lib/modules/shell.go
+++ b/lib/modules/shell.go
@@ -47,17 +47,19 @@
 	"os"
 	"strings"
 	"sync"
+	"time"
 
 	"veyron.io/veyron/veyron2/vlog"
 )
 
 // Shell represents the context within which commands are run.
 type Shell struct {
-	mu      sync.Mutex
-	env     map[string]string
-	cmds    map[string]*commandDesc
-	handles map[Handle]struct{}
-	credDir string
+	mu           sync.Mutex
+	env          map[string]string
+	cmds         map[string]*commandDesc
+	handles      map[Handle]struct{}
+	credDir      string
+	startTimeout time.Duration
 }
 
 type commandDesc struct {
@@ -90,9 +92,10 @@
 	// TODO(cnicolaou): should create a new identity if one doesn't
 	// already exist
 	sh := &Shell{
-		env:     make(map[string]string),
-		cmds:    make(map[string]*commandDesc),
-		handles: make(map[Handle]struct{}),
+		env:          make(map[string]string),
+		cmds:         make(map[string]*commandDesc),
+		handles:      make(map[Handle]struct{}),
+		startTimeout: time.Minute,
 	}
 	if flag.Lookup("test.run") != nil && os.Getenv("VEYRON_CREDENTIALS") == "" {
 		if err := sh.CreateAndUseNewCredentials(); err != nil {
@@ -181,7 +184,9 @@
 // ones override the Shell and the Shell ones override the OS ones.
 //
 // The Shell tracks all of the Handles that it creates so that it can shut
-// them down when asked to.
+// them down when asked to. The returned Handle may be non-nil even when an
+// error is returned, in which case it may be used to retrieve any output
+// from the failed command.
 //
 // Commands may have already been registered with the Shell using AddFunction
 // or AddSubprocess, but if not, they will treated as subprocess commands
@@ -196,7 +201,9 @@
 	expanded := append([]string{name}, sh.expand(args...)...)
 	h, err := cmd.factory().start(sh, cenv, expanded...)
 	if err != nil {
-		return nil, err
+		// If the error is a timeout, then h can be used to recover
+		// any output from the process.
+		return h, err
 	}
 	sh.mu.Lock()
 	sh.handles[h] = struct{}{}
@@ -214,6 +221,11 @@
 	return cmd
 }
 
+// SetStartTimeout sets the timeout for starting subcommands.
+func (sh *Shell) SetStartTimeout(d time.Duration) {
+	sh.startTimeout = d
+}
+
 // CommandEnvelope returns the command line and environment that would be
 // used for running the subprocess or function if it were started with the
 // specifed arguments.
diff --git a/runtimes/google/rt/rt.go b/runtimes/google/rt/rt.go
index d106b7f..6de8860 100644
--- a/runtimes/google/rt/rt.go
+++ b/runtimes/google/rt/rt.go
@@ -5,7 +5,6 @@
 	"fmt"
 	"os"
 	"path/filepath"
-	"strings"
 	"sync"
 
 	"veyron.io/veyron/veyron2"
@@ -17,6 +16,7 @@
 	"veyron.io/veyron/veyron2/rt"
 
 	"veyron.io/veyron/veyron/lib/exec"
+	"veyron.io/veyron/veyron/lib/flags"
 	_ "veyron.io/veyron/veyron/lib/stats/sysstats"
 	"veyron.io/veyron/veyron/runtimes/google/naming/namespace"
 	"veyron.io/veyron/veyron2/options"
@@ -40,6 +40,7 @@
 	store      security.PublicIDStore
 	client     ipc.Client
 	mgmt       *mgmtImpl
+	flags      flags.RuntimeFlags
 	nServers   int  // GUARDED_BY(mu)
 	cleaningUp bool // GUARDED_BY(mu)
 
@@ -49,14 +50,21 @@
 
 var _ veyron2.Runtime = (*vrt)(nil)
 
+var flagsOnce sync.Once
+var runtimeFlags *flags.Flags
+
 func init() {
 	rt.RegisterRuntime(veyron2.GoogleRuntimeName, New)
+	runtimeFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime)
 }
 
 // Implements veyron2/rt.New
 func New(opts ...veyron2.ROpt) (veyron2.Runtime, error) {
 	rt := &vrt{mgmt: new(mgmtImpl), lang: i18n.LangIDFromEnv(), program: filepath.Base(os.Args[0])}
-	flag.Parse()
+	flagsOnce.Do(func() {
+		runtimeFlags.Parse(os.Args[1:])
+	})
+	rt.flags = runtimeFlags.RuntimeFlags()
 	nsRoots := []string{}
 	for _, o := range opts {
 		switch v := o.(type) {
@@ -66,8 +74,6 @@
 			rt.id = v.PrivateID
 		case options.Profile:
 			rt.profile = v.Profile
-		case options.NamespaceRoots:
-			nsRoots = v
 		case options.RuntimeName:
 			if v != "google" && v != "" {
 				return nil, fmt.Errorf("%q is the wrong name for this runtime", v)
@@ -88,20 +94,7 @@
 		vlog.VI(1).Infof("Using profile %q", rt.profile.Name())
 	}
 
-	if len(nsRoots) == 0 {
-		for _, ev := range os.Environ() {
-			p := strings.SplitN(ev, "=", 2)
-			if len(p) != 2 {
-				continue
-			}
-			k, v := p[0], p[1]
-			if strings.HasPrefix(k, "NAMESPACE_ROOT") {
-				nsRoots = append(nsRoots, v)
-			}
-		}
-	}
-
-	if ns, err := namespace.New(rt, nsRoots...); err != nil {
+	if ns, err := namespace.New(rt, rt.flags.NamespaceRoots...); err != nil {
 		return nil, fmt.Errorf("Couldn't create mount table: %v", err)
 	} else {
 		rt.ns = ns
@@ -113,7 +106,7 @@
 		return nil, fmt.Errorf("failed to create stream manager: %s", err)
 	}
 
-	if err := rt.initSecurity(); err != nil {
+	if err := rt.initSecurity(rt.flags.Credentials); err != nil {
 		return nil, fmt.Errorf("failed to init sercurity: %s", err)
 	}
 
diff --git a/runtimes/google/rt/rt_test.go b/runtimes/google/rt/rt_test.go
index cb1832e..44b28fe 100644
--- a/runtimes/google/rt/rt_test.go
+++ b/runtimes/google/rt/rt_test.go
@@ -10,7 +10,6 @@
 	"testing"
 	"time"
 
-	"veyron.io/veyron/veyron2"
 	"veyron.io/veyron/veyron2/naming"
 	"veyron.io/veyron/veyron2/options"
 	"veyron.io/veyron/veyron2/rt"
@@ -20,7 +19,6 @@
 	"veyron.io/veyron/veyron/lib/expect"
 	"veyron.io/veyron/veyron/lib/modules"
 	"veyron.io/veyron/veyron/lib/testutil"
-	irt "veyron.io/veyron/veyron/runtimes/google/rt"
 	vsecurity "veyron.io/veyron/veyron/security"
 )
 
@@ -28,6 +26,10 @@
 	local security.Principal
 }
 
+// Environment variable pointing to a directory where information about a
+// principal (private key, blessing store, blessing roots etc.) is stored.
+const veyronCredentialsEnvVar = "VEYRON_CREDENTIALS"
+
 func (*context) Method() string                            { return "" }
 func (*context) Name() string                              { return "" }
 func (*context) Suffix() string                            { return "" }
@@ -44,6 +46,9 @@
 func init() {
 	testutil.Init()
 	modules.RegisterChild("child", "", child)
+	modules.RegisterChild("principal", "", principal)
+	modules.RegisterChild("mutate", "", mutatePrincipal)
+	modules.RegisterChild("runner", "", runner)
 }
 
 func TestHelperProcess(t *testing.T) {
@@ -71,7 +76,7 @@
 	vlog.Infof("%s\n", r.Logger())
 	fmt.Fprintf(stdout, "%s\n", r.Logger())
 	modules.WaitForEOF(stdin)
-	fmt.Printf("done\n")
+	fmt.Fprintf(stdout, "done\n")
 	return nil
 }
 
@@ -99,67 +104,221 @@
 	h.Shutdown(os.Stderr, os.Stderr)
 }
 
-func TestInitPrincipal(t *testing.T) {
-	newRT := func() veyron2.Runtime {
-		r, err := rt.New(profileOpt)
-		if err != nil {
-			t.Fatalf("rt.New failed: %v", err)
-		}
-		return r
+func validatePrincipal(p security.Principal) error {
+	if p == nil {
+		return fmt.Errorf("nil principal")
 	}
-	testPrincipal := func(r veyron2.Runtime) security.Principal {
-		p := r.Principal()
-		if p == nil {
-			t.Fatalf("rt.Principal() returned nil")
-		}
-		blessings := p.BlessingStore().Default()
-		if blessings == nil {
-			t.Fatalf("rt.Principal().BlessingStore().Default() returned nil")
+	blessings := p.BlessingStore().Default()
+	if blessings == nil {
+		return fmt.Errorf("rt.Principal().BlessingStore().Default() returned nil")
 
-		}
-		if n := len(blessings.ForContext(&context{local: p})); n != 1 {
-			t.Fatalf("rt.Principal().BlessingStore().Default() returned Blessing %v with %d recognized blessings, want exactly one recognized blessing", blessings, n)
-		}
-		return p
 	}
-	origCredentialsDir := os.Getenv(irt.VeyronCredentialsEnvVar)
-	defer os.Setenv(irt.VeyronCredentialsEnvVar, origCredentialsDir)
-
-	// Test that even with VEYRON_CREDENTIALS unset the runtime's Principal
-	// is correctly initialized.
-	if err := os.Setenv(irt.VeyronCredentialsEnvVar, ""); err != nil {
-		t.Fatal(err)
+	if n := len(blessings.ForContext(&context{local: p})); n != 1 {
+		fmt.Errorf("rt.Principal().BlessingStore().Default() returned Blessing %v with %d recognized blessings, want exactly one recognized blessing", blessings, n)
 	}
-	testPrincipal(newRT())
+	return nil
+}
 
-	// Test that with VEYRON_CREDENTIALS set the runtime's Principal is correctly
-	// initialized.
-	credentials, err := ioutil.TempDir("", "credentials")
+func tmpDir(t *testing.T) string {
+	dir, err := ioutil.TempDir("", "rt_test_dir")
 	if err != nil {
-		t.Fatal(err)
+		t.Fatalf("unexpected error: %s", err)
 	}
-	defer os.RemoveAll(credentials)
-	if err := os.Setenv(irt.VeyronCredentialsEnvVar, credentials); err != nil {
-		t.Fatal(err)
+	return dir
+}
+
+func principal(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+	r := rt.Init()
+	err := validatePrincipal(r.Principal())
+	fmt.Fprintf(stdout, "ERROR=%v\n", err)
+	fmt.Fprintf(stdout, "PUBKEY=%s\n", r.Principal().PublicKey())
+	modules.WaitForEOF(stdin)
+	return nil
+}
+
+// Runner runs a principal as a subprocess and reports back with its
+// own security info and it's childs.
+func runner(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+	r := rt.Init()
+	err := validatePrincipal(r.Principal())
+	fmt.Fprintf(stdout, "RUNNER_ERROR=%v\n", err)
+	fmt.Fprintf(stdout, "RUNNER_PUBKEY=%s\n", r.Principal().PublicKey())
+	if err != nil {
+		return err
 	}
-	p := testPrincipal(newRT())
+	sh := modules.NewShell("principal")
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h, err := sh.Start("principal", nil, args[1:]...)
+	if err != nil {
+		return err
+	}
+	s := expect.NewSession(nil, h.Stdout(), 1*time.Second) // time.Minute)
+	fmt.Fprintf(stdout, s.ReadLine()+"\n")
+	fmt.Fprintf(stdout, s.ReadLine()+"\n")
+	modules.WaitForEOF(stdin)
+	return nil
+}
+
+func mutatePrincipal(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+	r := rt.Init()
+
+	rtPrincipal := r.Principal()
+	err := validatePrincipal(rtPrincipal)
+	fmt.Fprintf(stdout, "ERROR=%v\n", err)
 
 	// Mutate the roots and store of this principal.
-	blessing, err := p.BlessSelf("irrelevant")
+	blessing, err := rtPrincipal.BlessSelf("irrelevant")
 	if err != nil {
-		t.Fatal(err)
+		return err
 	}
-	if _, err := p.BlessingStore().Set(blessing, security.AllPrincipals); err != nil {
-		t.Fatal(err)
+	if _, err := rtPrincipal.BlessingStore().Set(blessing, security.AllPrincipals); err != nil {
+		return err
 	}
-	if err := p.AddToRoots(blessing); err != nil {
+	if err := rtPrincipal.AddToRoots(blessing); err != nil {
+		return err
+	}
+	newRT, err := rt.New(profileOpt)
+	if err != nil {
+		return fmt.Errorf("rt.New failed: %v", err)
+	}
+	// Test that the same principal gets initialized on creating a new runtime
+	// from the same credentials directory.
+	if got := newRT.Principal(); !reflect.DeepEqual(got, rtPrincipal) {
+		return fmt.Errorf("Initialized Principal: %v, expected: %v", got.PublicKey(), rtPrincipal.PublicKey())
+	}
+	fmt.Fprintf(stdout, "PUBKEY=%s\n", newRT.Principal().PublicKey())
+	modules.WaitForEOF(stdin)
+	return nil
+}
+
+func createCredentialsInDir(t *testing.T, dir string) security.Principal {
+	principal, err := vsecurity.CreatePersistentPrincipal(dir, nil)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	vsecurity.InitDefaultBlessings(principal, "test")
+	return principal
+}
+
+func TestPrincipalInheritance(t *testing.T) {
+	sh := modules.NewShell("principal", "runner")
+	defer func() {
+		sh.Cleanup(os.Stdout, os.Stderr)
+	}()
+
+	// Test that the child inherits the parent's credentials correctly.
+	// The running test process may or may not have a credentials directory set
+	// up so we have to use a 'runner' process to ensure the correct setup.
+	cdir := tmpDir(t)
+	defer os.RemoveAll(cdir)
+
+	principal := createCredentialsInDir(t, cdir)
+
+	// directory supplied by the environment.
+	credEnv := []string{veyronCredentialsEnvVar + "=" + cdir}
+
+	h, err := sh.Start("runner", credEnv)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	s := expect.NewSession(t, h.Stdout(), 2*time.Second) //time.Minute)
+	runnerErr := s.ExpectVar("RUNNER_ERROR")
+	runnerPubKey := s.ExpectVar("RUNNER_PUBKEY")
+	principalErr := s.ExpectVar("ERROR")
+	principalPubKey := s.ExpectVar("PUBKEY")
+	if err := s.Error(); err != nil {
+		t.Fatalf("failed to read input from children: %s", err)
+	}
+	h.Shutdown(os.Stdout, os.Stderr)
+	if runnerErr != "<nil>" || principalErr != "<nil>" {
+		t.Fatalf("unexpected error: runner %q, principal %q", runnerErr, principalErr)
+	}
+	pubKey := principal.PublicKey().String()
+	if runnerPubKey != pubKey || principalPubKey != pubKey {
+		t.Fatalf("unexpected pubkeys: expected %s: runner %s, principal %s",
+			pubKey, runnerPubKey, principalPubKey)
+	}
+
+}
+
+func TestPrincipalInit(t *testing.T) {
+	// Collet the process' public key and error status
+	collect := func(sh *modules.Shell, cmd string, env []string, args ...string) (string, error) {
+		h, err := sh.Start(cmd, env, args...)
+		if err != nil {
+			t.Fatalf("unexpected error: %s", err)
+		}
+		s := expect.NewSession(t, h.Stdout(), time.Minute)
+		s.SetVerbosity(testing.Verbose())
+		errstr := s.ExpectVar("ERROR")
+		pubkey := s.ExpectVar("PUBKEY")
+		if errstr != "<nil>" {
+			return pubkey, fmt.Errorf("%s", errstr)
+		}
+		return pubkey, nil
+	}
+
+	// A credentials directory may, or may, not have been already specified.
+	// Either way, we want to use our own, so we set it aside and use our own.
+	origCredentialsDir := os.Getenv(veyronCredentialsEnvVar)
+	defer os.Setenv(veyronCredentialsEnvVar, origCredentialsDir)
+
+	// Test that with VEYRON_CREDENTIALS unset the runtime's Principal
+	// is correctly initialized.
+	if err := os.Setenv(veyronCredentialsEnvVar, ""); err != nil {
 		t.Fatal(err)
 	}
 
-	// Test that the same principal gets initialized on creating a new runtime
-	// from the same credentials directory.
-	if got := newRT().Principal(); !reflect.DeepEqual(got, p) {
-		t.Fatalf("Initialized Principal: %v, expected: %v", got.PublicKey(), p.PublicKey())
+	sh := modules.NewShell("principal")
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+
+	pubkey, err := collect(sh, "principal", nil)
+	if err != nil {
+		t.Fatalf("child failed to create+validate principal: %v", err)
+	}
+	if len(pubkey) == 0 {
+		t.Fatalf("child failed to return a public key")
+	}
+
+	// Test specifying credentials via VEYRON_CREDENTIALS
+	cdir1 := tmpDir(t)
+	defer os.RemoveAll(cdir1)
+	principal := createCredentialsInDir(t, cdir1)
+	// directory supplied by the environment.
+	credEnv := []string{veyronCredentialsEnvVar + "=" + cdir1}
+
+	pubkey, err = collect(sh, "principal", credEnv)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+
+	if got, want := pubkey, principal.PublicKey().String(); got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+
+	// Test specifying credentials via the command line and that the
+	// comand line overrides the environment
+	cdir2 := tmpDir(t)
+	defer os.RemoveAll(cdir2)
+	clPrincipal := createCredentialsInDir(t, cdir2)
+
+	pubkey, err = collect(sh, "principal", credEnv, "--veyron.credentials="+cdir2)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+
+	if got, want := pubkey, clPrincipal.PublicKey().String(); got != want {
+		t.Errorf("got %q, want %q", got, want)
+	}
+
+	// Mutate the roots and store of the principal in the child process.
+	pubkey, err = collect(sh, "mutate", credEnv, "--veyron.credentials="+cdir2)
+	if err != nil {
+		t.Errorf("unexpected error: %s", err)
+	}
+
+	if got, want := pubkey, clPrincipal.PublicKey().String(); got != want {
+		t.Errorf("got %q, want %q", got, want)
 	}
 }
 
diff --git a/runtimes/google/rt/security.go b/runtimes/google/rt/security.go
index 0863c04a..aa673d4 100644
--- a/runtimes/google/rt/security.go
+++ b/runtimes/google/rt/security.go
@@ -15,10 +15,6 @@
 	"veyron.io/veyron/veyron2/vlog"
 )
 
-// Environment variable pointing to a directory where information about a principal
-// (private key, blessing store, blessing roots etc.) is stored.
-const VeyronCredentialsEnvVar = "VEYRON_CREDENTIALS"
-
 func (rt *vrt) Principal() security.Principal {
 	return rt.principal
 }
@@ -35,31 +31,33 @@
 	return rt.store
 }
 
-func (rt *vrt) initSecurity() error {
+func (rt *vrt) initSecurity(credentials string) error {
 	if err := rt.initOldSecurity(); err != nil {
 		return err
 	}
-	if err := rt.initPrincipal(); err != nil {
+	if err := rt.initPrincipal(credentials); err != nil {
 		return fmt.Errorf("principal initialization failed: %v", err)
 	}
 	return nil
 }
 
-func (rt *vrt) initPrincipal() error {
+func (rt *vrt) initPrincipal(credentials string) error {
 	if rt.principal != nil {
 		return nil
 	}
 	var err error
+	// TODO(cnicolaou,ashankar,ribrdb): this should be supplied via
+	// the exec.GetChildHandle call.
 	if len(os.Getenv(agent.FdVarName)) > 0 {
 		rt.principal, err = rt.connectToAgent()
 		return err
-	} else if dir := os.Getenv(VeyronCredentialsEnvVar); len(dir) > 0 {
+	} else if len(credentials) > 0 {
 		// TODO(ataly, ashankar): If multiple runtimes are getting
 		// initialized at the same time from the same VEYRON_CREDENTIALS
 		// we will need some kind of locking for the credential files.
-		if rt.principal, err = vsecurity.LoadPersistentPrincipal(dir, nil); err != nil {
+		if rt.principal, err = vsecurity.LoadPersistentPrincipal(credentials, nil); err != nil {
 			if os.IsNotExist(err) {
-				if rt.principal, err = vsecurity.CreatePersistentPrincipal(dir, nil); err != nil {
+				if rt.principal, err = vsecurity.CreatePersistentPrincipal(credentials, nil); err != nil {
 					return err
 				}
 				return vsecurity.InitDefaultBlessings(rt.principal, defaultBlessingName())
diff --git a/security/agent/agentd/main.go b/security/agent/agentd/main.go
index b3341cb..423e146 100644
--- a/security/agent/agentd/main.go
+++ b/security/agent/agentd/main.go
@@ -1,11 +1,12 @@
 package main
 
 import (
-	"code.google.com/p/gopass"
+	"code.google.com/p/go.crypto/ssh/terminal"
 	"flag"
 	"fmt"
 	"os"
 	"os/exec"
+	"os/signal"
 	"syscall"
 	_ "veyron.io/veyron/veyron/profiles"
 	vsecurity "veyron.io/veyron/veyron/security"
@@ -89,11 +90,11 @@
 
 func handleDoesNotExist(dir string) (security.Principal, error) {
 	fmt.Println("Private key file does not exist. Creating new private key...")
-	pass, err := gopass.GetPass("Enter passphrase (entering nothing will store unecrypted): ")
+	pass, err := getPassword("Enter passphrase (entering nothing will store unecrypted): ")
 	if err != nil {
 		return nil, fmt.Errorf("failed to read passphrase: %v", err)
 	}
-	p, err := vsecurity.CreatePersistentPrincipal(dir, []byte(pass))
+	p, err := vsecurity.CreatePersistentPrincipal(dir, pass)
 	if err != nil {
 		return nil, err
 	}
@@ -103,9 +104,40 @@
 
 func handlePassphrase(dir string) (security.Principal, error) {
 	fmt.Println("Private key file is encrypted. Please enter passphrase.")
-	pass, err := gopass.GetPass("Enter passphrase: ")
+	pass, err := getPassword("Enter passphrase: ")
 	if err != nil {
 		return nil, fmt.Errorf("failed to read passphrase: %v", err)
 	}
-	return vsecurity.LoadPersistentPrincipal(dir, []byte(pass))
+	return vsecurity.LoadPersistentPrincipal(dir, pass)
+}
+
+func getPassword(prompt string) ([]byte, error) {
+	fmt.Printf(prompt)
+	stop := make(chan bool)
+	defer close(stop)
+	state, err := terminal.GetState(int(os.Stdin.Fd()))
+	if err != nil {
+		return nil, err
+	}
+	go catchTerminationSignals(stop, state)
+	return terminal.ReadPassword(int(os.Stdin.Fd()))
+}
+
+// catchTerminationSignals catches signals to allow us to turn terminal echo back on.
+func catchTerminationSignals(stop <-chan bool, state *terminal.State) {
+	var successErrno syscall.Errno
+	sig := make(chan os.Signal, 4)
+	// Catch the blockable termination signals.
+	signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGHUP)
+	select {
+	case <-sig:
+		// Start on new line in terminal.
+		fmt.Printf("\n")
+		if err := terminal.Restore(int(os.Stdin.Fd()), state); err != successErrno {
+			vlog.Errorf("Failed to restore terminal state (%v), you words may not show up when you type, enter 'stty echo' to fix this.", err)
+		}
+		os.Exit(-1)
+	case <-stop:
+		signal.Stop(sig)
+	}
 }
diff --git a/tools/associate/doc.go b/tools/associate/doc.go
new file mode 100644
index 0000000..5be1076
--- /dev/null
+++ b/tools/associate/doc.go
@@ -0,0 +1,72 @@
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+
+/*
+The associate tool facilitates creating blessing to system account associations.
+
+Usage:
+   associate <command>
+
+The associate commands are:
+   list        Lists the account associations.
+   add         Associate the listed blessings with the specified system account
+   remove      Removes system accounts associated with the listed blessings.
+   help        Display help for commands or topics
+Run "associate help [command]" for command usage.
+
+The global flags are:
+   -alsologtostderr=true: log to standard error as well as files
+   -log_backtrace_at=:0: when logging hits line file:N, emit a stack trace
+   -log_dir=: if non-empty, write log files to this directory
+   -logtostderr=false: log to standard error instead of files
+   -max_stack_buf_size=4292608: max size in bytes of the buffer to use for logging stack traces
+   -stderrthreshold=2: logs at or above this threshold go to stderr
+   -v=0: log level for V logs
+   -vmodule=: comma-separated list of pattern=N settings for file-filtered logging
+   -vv=0: log level for V logs
+
+Associate List
+
+Lists all account associations 
+
+Usage:
+   associate list <nodemanager>.
+
+<nodemanager> is the name of the node manager to connect to.
+
+Associate Add
+
+Associate the listed blessings with the specified system account
+
+Usage:
+   associate add <nodemanager> <systemName> <blessing>...
+
+<identify specifier>... is a list of 1 or more identify specifications
+<systemName> is the name of an account holder on the local system
+<blessing>.. are the blessings to associate systemAccount with
+
+Associate Remove
+
+Removes system accounts associated with the listed blessings.
+
+Usage:
+   associate remove <nodemanager>  <blessing>...
+
+<nodemanager> is the node manager to connect to
+<blessing>... is a list of blessings.
+
+Associate Help
+
+Help with no args displays the usage of the parent command.
+Help with args displays the usage of the specified sub-command or help topic.
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+   associate help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The help flags are:
+   -style=text: The formatting style for help output, either "text" or "godoc".
+*/
+package main
diff --git a/tools/associate/impl.go b/tools/associate/impl.go
new file mode 100644
index 0000000..a60f649
--- /dev/null
+++ b/tools/associate/impl.go
@@ -0,0 +1,104 @@
+package main
+
+import (
+	"fmt"
+	"time"
+
+	"veyron.io/veyron/veyron/lib/cmdline"
+
+	"veyron.io/veyron/veyron2/rt"
+	"veyron.io/veyron/veyron2/services/mgmt/node"
+)
+
+var cmdList = &cmdline.Command{
+	Run:      runList,
+	Name:     "list",
+	Short:    "Lists the account associations.",
+	Long:     "Lists all account associations.",
+	ArgsName: "<nodemanager>.",
+	ArgsLong: `
+<nodemanager> is the name of the node manager to connect to.`,
+}
+
+func runList(cmd *cmdline.Command, args []string) error {
+	if expected, got := 1, len(args); expected != got {
+		return cmd.UsageErrorf("list: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+
+	ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute)
+	defer cancel()
+	nodeStub, err := node.BindNode(args[0])
+	if err != nil {
+		return fmt.Errorf("BindNode(%s) failed: %v", args[0], err)
+	}
+	assocs, err := nodeStub.ListAssociations(ctx)
+	if err != nil {
+		return fmt.Errorf("ListAssociations failed: %v", err)
+	}
+
+	for _, a := range assocs {
+		fmt.Fprintf(cmd.Stdout(), "%s %s\n", a.IdentityName, a.AccountName)
+	}
+	return nil
+}
+
+var cmdAdd = &cmdline.Command{
+	Run:      runAdd,
+	Name:     "add",
+	Short:    "Add the listed blessings with the specified system account.",
+	Long:     "Add the listed blessings with the specified system account.",
+	ArgsName: "<nodemanager> <systemName> <blessing>...",
+	ArgsLong: `
+<nodemanager> is the name of the node manager to connect to.
+<systemName> is the name of an account holder on the local system.
+<blessing>.. are the blessings to associate systemAccount with.`,
+}
+
+func runAdd(cmd *cmdline.Command, args []string) error {
+	if expected, got := 3, len(args); got < expected {
+		return cmd.UsageErrorf("add: incorrect number of arguments, expected at least %d, got %d", expected, got)
+	}
+	ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute)
+	defer cancel()
+	nodeStub, err := node.BindNode(args[0])
+	if err != nil {
+		return fmt.Errorf("BindNode(%s) failed: %v", args[0], err)
+	}
+	return nodeStub.AssociateAccount(ctx, args[2:], args[1])
+}
+
+var cmdRemove = &cmdline.Command{
+	Run:      runRemove,
+	Name:     "remove",
+	Short:    "Removes system accounts associated with the listed blessings.",
+	Long:     "Removes system accounts associated with the listed blessings.",
+	ArgsName: "<nodemanager>  <blessing>...",
+	ArgsLong: `
+<nodemanager> is the name of the node manager to connect to.
+<blessing>... is a list of blessings.`,
+}
+
+func runRemove(cmd *cmdline.Command, args []string) error {
+	if expected, got := 2, len(args); got < expected {
+		return cmd.UsageErrorf("remove: incorrect number of arguments, expected at least %d, got %d", expected, got)
+	}
+	ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute)
+	defer cancel()
+	nodeStub, err := node.BindNode(args[0])
+	if err != nil {
+		return fmt.Errorf("BindNode(%s) failed: %v", args[0], err)
+	}
+
+	return nodeStub.AssociateAccount(ctx, args[1:], "")
+}
+
+func root() *cmdline.Command {
+	return &cmdline.Command{
+		Name:  "associate",
+		Short: "Tool for creating associations between Vanadium blessings and a system account",
+		Long: `
+The associate tool facilitates managing blessing to system account associations.
+`,
+		Children: []*cmdline.Command{cmdList, cmdAdd, cmdRemove},
+	}
+}
diff --git a/tools/associate/impl_test.go b/tools/associate/impl_test.go
new file mode 100644
index 0000000..7043fcf
--- /dev/null
+++ b/tools/associate/impl_test.go
@@ -0,0 +1,266 @@
+package main
+
+import (
+	"bytes"
+	"reflect"
+	"strings"
+	"testing"
+
+	"veyron.io/veyron/veyron2/security"
+	"veyron.io/veyron/veyron2/services/mgmt/binary"
+	"veyron.io/veyron/veyron2/services/mgmt/node"
+	"veyron.io/veyron/veyron2/services/mounttable"
+
+	"veyron.io/veyron/veyron/profiles"
+	"veyron.io/veyron/veyron2"
+	"veyron.io/veyron/veyron2/ipc"
+	"veyron.io/veyron/veyron2/naming"
+	"veyron.io/veyron/veyron2/rt"
+	"veyron.io/veyron/veyron2/vlog"
+)
+
+type mockNodeInvoker struct {
+	tape *Tape
+	t    *testing.T
+}
+
+type ListAssociationResponse struct {
+	na  []node.Association
+	err error
+}
+
+func (mni *mockNodeInvoker) ListAssociations(ipc.ServerContext) (associations []node.Association, err error) {
+	vlog.VI(2).Infof("ListAssociations() was called")
+
+	ir := mni.tape.Record("ListAssociations")
+	r := ir.(ListAssociationResponse)
+	return r.na, r.err
+}
+
+type AddAssociationStimulus struct {
+	fun           string
+	identityNames []string
+	accountName   string
+}
+
+func (i *mockNodeInvoker) AssociateAccount(call ipc.ServerContext, identityNames []string, accountName string) error {
+	ri := i.tape.Record(AddAssociationStimulus{"AssociateAccount", identityNames, accountName})
+	switch r := ri.(type) {
+	case nil:
+		return nil
+	case error:
+		return r
+	}
+	i.t.Fatalf("AssociateAccount (mock) response %v is of bad type", ri)
+	return nil
+}
+
+func (i *mockNodeInvoker) Claim(call ipc.ServerContext) error { return nil }
+func (*mockNodeInvoker) Describe(ipc.ServerContext) (node.Description, error) {
+	return node.Description{}, nil
+}
+func (*mockNodeInvoker) IsRunnable(_ ipc.ServerContext, description binary.Description) (bool, error) {
+	return false, nil
+}
+func (*mockNodeInvoker) Reset(call ipc.ServerContext, deadline uint64) error               { return nil }
+func (*mockNodeInvoker) Install(ipc.ServerContext, string) (string, error)                 { return "", nil }
+func (*mockNodeInvoker) Refresh(ipc.ServerContext) error                                   { return nil }
+func (*mockNodeInvoker) Restart(ipc.ServerContext) error                                   { return nil }
+func (*mockNodeInvoker) Resume(ipc.ServerContext) error                                    { return nil }
+func (i *mockNodeInvoker) Revert(call ipc.ServerContext) error                             { return nil }
+func (*mockNodeInvoker) Start(ipc.ServerContext) ([]string, error)                         { return []string{}, nil }
+func (*mockNodeInvoker) Stop(ipc.ServerContext, uint32) error                              { return nil }
+func (*mockNodeInvoker) Suspend(ipc.ServerContext) error                                   { return nil }
+func (*mockNodeInvoker) Uninstall(ipc.ServerContext) error                                 { return nil }
+func (i *mockNodeInvoker) Update(ipc.ServerContext) error                                  { return nil }
+func (*mockNodeInvoker) UpdateTo(ipc.ServerContext, string) error                          { return nil }
+func (i *mockNodeInvoker) SetACL(ipc.ServerContext, security.ACL, string) error { return nil }
+func (i *mockNodeInvoker) GetACL(ipc.ServerContext) (security.ACL, string, error) {
+	return security.ACL{}, "", nil
+}
+func (i *mockNodeInvoker) Glob(ctx ipc.ServerContext, pattern string, stream mounttable.GlobbableServiceGlobStream) error {
+	return nil
+}
+
+type dispatcher struct {
+	tape *Tape
+	t    *testing.T
+}
+
+func NewDispatcher(t *testing.T, tape *Tape) *dispatcher {
+	return &dispatcher{tape: tape, t: t}
+}
+
+func (d *dispatcher) Lookup(suffix, method string) (ipc.Invoker, security.Authorizer, error) {
+	invoker := ipc.ReflectInvoker(node.NewServerNode(&mockNodeInvoker{tape: d.tape, t: d.t}))
+	return invoker, nil, nil
+}
+
+func startServer(t *testing.T, r veyron2.Runtime, tape *Tape) (ipc.Server, naming.Endpoint, error) {
+	dispatcher := NewDispatcher(t, tape)
+	server, err := r.NewServer()
+	if err != nil {
+		t.Errorf("NewServer failed: %v", err)
+		return nil, nil, err
+	}
+	endpoint, err := server.Listen(profiles.LocalListenSpec)
+	if err != nil {
+		t.Errorf("Listen failed: %v", err)
+		stopServer(t, server)
+		return nil, nil, err
+	}
+	if err := server.Serve("", dispatcher); err != nil {
+		t.Errorf("Serve failed: %v", err)
+		stopServer(t, server)
+		return nil, nil, err
+	}
+	return server, endpoint, nil
+}
+
+func stopServer(t *testing.T, server ipc.Server) {
+	if err := server.Stop(); err != nil {
+		t.Errorf("server.Stop failed: %v", err)
+	}
+}
+
+func TestListCommand(t *testing.T) {
+	runtime := rt.Init()
+	tape := NewTape()
+	server, endpoint, err := startServer(t, runtime, tape)
+	if err != nil {
+		return
+	}
+	defer stopServer(t, server)
+
+	// Setup the command-line.
+	cmd := root()
+	var stdout, stderr bytes.Buffer
+	cmd.Init(nil, &stdout, &stderr)
+	nodeName := naming.JoinAddressName(endpoint.String(), "")
+
+	// Test the 'list' command.
+	tape.SetResponses([]interface{}{ListAssociationResponse{
+		na: []node.Association{
+			{
+				"root/self",
+				"alice_self_account",
+			},
+			{
+				"root/other",
+				"alice_other_account",
+			},
+		},
+		err: nil,
+	}})
+
+	if err := cmd.Execute([]string{"list", nodeName}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	if expected, got := "root/self alice_self_account\nroot/other alice_other_account", strings.TrimSpace(stdout.String()); got != expected {
+		t.Fatalf("Unexpected output from list. Got %q, expected %q", got, expected)
+	}
+	if got, expected := tape.Play(), []interface{}{"ListAssociations"}; !reflect.DeepEqual(expected, got) {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+	tape.Rewind()
+	stdout.Reset()
+
+	// Test list with bad parameters.
+	if err := cmd.Execute([]string{"list", nodeName, "hello"}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	if got, expected := len(tape.Play()), 0; got != expected {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+	tape.Rewind()
+	stdout.Reset()
+}
+
+func TestAddCommand(t *testing.T) {
+	runtime := rt.Init()
+	tape := NewTape()
+	server, endpoint, err := startServer(t, runtime, tape)
+	if err != nil {
+		return
+	}
+	defer stopServer(t, server)
+
+	// Setup the command-line.
+	cmd := root()
+	var stdout, stderr bytes.Buffer
+	cmd.Init(nil, &stdout, &stderr)
+	nodeName := naming.JoinAddressName(endpoint.String(), "//myapp/1")
+
+	if err := cmd.Execute([]string{"add", "one"}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	if got, expected := len(tape.Play()), 0; got != expected {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+	tape.Rewind()
+	stdout.Reset()
+
+	tape.SetResponses([]interface{}{nil})
+	if err := cmd.Execute([]string{"add", nodeName, "alice", "root/self"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	expected := []interface{}{
+		AddAssociationStimulus{"AssociateAccount", []string{"root/self"}, "alice"},
+	}
+	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("unexpected result. Got %v want %v", got, expected)
+	}
+	tape.Rewind()
+	stdout.Reset()
+
+	tape.SetResponses([]interface{}{nil})
+	if err := cmd.Execute([]string{"add", nodeName, "alice", "root/other", "root/self"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	expected = []interface{}{
+		AddAssociationStimulus{"AssociateAccount", []string{"root/other", "root/self"}, "alice"},
+	}
+	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("unexpected result. Got %v want %v", got, expected)
+	}
+	tape.Rewind()
+	stdout.Reset()
+}
+
+func TestRemoveCommand(t *testing.T) {
+	runtime := rt.Init()
+	tape := NewTape()
+	server, endpoint, err := startServer(t, runtime, tape)
+	if err != nil {
+		return
+	}
+	defer stopServer(t, server)
+
+	// Setup the command-line.
+	cmd := root()
+	var stdout, stderr bytes.Buffer
+	cmd.Init(nil, &stdout, &stderr)
+	nodeName := naming.JoinAddressName(endpoint.String(), "//myapp/1")
+
+	if err := cmd.Execute([]string{"remove", "one"}); err == nil {
+		t.Fatalf("wrongly failed to receive a non-nil error.")
+	}
+	if got, expected := len(tape.Play()), 0; got != expected {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+	tape.Rewind()
+	stdout.Reset()
+
+	tape.SetResponses([]interface{}{nil})
+	if err := cmd.Execute([]string{"remove", nodeName, "root/self"}); err != nil {
+		t.Fatalf("%v", err)
+	}
+	expected := []interface{}{
+		AddAssociationStimulus{"AssociateAccount", []string{"root/self"}, ""},
+	}
+	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
+		t.Errorf("unexpected result. Got %v want %v", got, expected)
+	}
+	tape.Rewind()
+	stdout.Reset()
+}
diff --git a/tools/associate/main.go b/tools/associate/main.go
new file mode 100644
index 0000000..a99a159
--- /dev/null
+++ b/tools/associate/main.go
@@ -0,0 +1,21 @@
+// The following enables go generate to generate the doc.go file.
+// Things to look out for:
+// 1) go:generate evaluates double-quoted strings into a single argument.
+// 2) go:generate performs $NAME expansion, so the bash cmd can't contain '$'.
+// 3) We generate into a *.tmp file first, otherwise go run will pick up the
+//    initially empty *.go file, and fail.
+//
+//go:generate bash -c "{ echo -e '// This file was auto-generated via go generate.\n// DO NOT UPDATE MANUALLY\n\n/*' && veyron go run *.go help -style=godoc ... && echo -e '*/\npackage main'; } > ./doc.go.tmp && mv ./doc.go.tmp ./doc.go"
+
+package main
+
+import (
+	"veyron.io/veyron/veyron2/rt"
+
+	_ "veyron.io/veyron/veyron/profiles"
+)
+
+func main() {
+	defer rt.Init().Cleanup()
+	root().Main()
+}
diff --git a/tools/associate/mock_test.go b/tools/associate/mock_test.go
new file mode 100644
index 0000000..936fc45
--- /dev/null
+++ b/tools/associate/mock_test.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+	"fmt"
+)
+
+type Tape struct {
+	stimuli   []interface{}
+	responses []interface{}
+}
+
+func (r *Tape) Record(call interface{}) interface{} {
+	r.stimuli = append(r.stimuli, call)
+
+	if len(r.responses) < 1 {
+		return fmt.Errorf("Record(%#v) had no response", call)
+	}
+	resp := r.responses[0]
+	r.responses = r.responses[1:]
+	return resp
+}
+
+func (r *Tape) SetResponses(responses []interface{}) {
+	r.responses = responses
+}
+
+func (r *Tape) Rewind() {
+	r.stimuli = make([]interface{}, 0)
+	r.responses = make([]interface{}, 0)
+}
+
+func (r *Tape) Play() []interface{} {
+	return r.stimuli
+}
+
+func NewTape() *Tape {
+	tape := new(Tape)
+	tape.Rewind()
+	return tape
+}