Merge "veyron/tools/mgmt/vsh: introducing vsh, a security-agent shell wrapper"
diff --git a/runtimes/google/ipc/context.go b/runtimes/google/ipc/context.go
index c8d23a3..28dff36 100644
--- a/runtimes/google/ipc/context.go
+++ b/runtimes/google/ipc/context.go
@@ -8,11 +8,13 @@
 	"veyron.io/veyron/veyron2/context"
 )
 
+const nilRuntimeMessage = "attempting to create a context with a nil runtime"
+
 // InternalNewContext creates a new context.T.  This function should only
 // be called from within the runtime implementation.
 func InternalNewContext(runtime veyron2.Runtime) context.T {
 	if runtime == nil {
-		panic("attempting to create a context with a nil runtime")
+		panic(nilRuntimeMessage)
 	}
 	return rootContext{runtime}
 }
@@ -133,9 +135,6 @@
 
 	return
 }
-func (c *cancelContext) parent() context.T {
-	return c.T
-}
 
 // addChild sets child as a descendant cancellable context. This
 // allows us to propagate cancellations through the context tree.
@@ -211,7 +210,7 @@
 		}
 		parent = c.parent()
 	}
-	return nil, false
+	return nil, false // Unreachable.
 }
 
 func (c *cancelContext) Done() <-chan struct{} { return c.done }
diff --git a/runtimes/google/ipc/context_test.go b/runtimes/google/ipc/context_test.go
index 7b9137c..515ee26 100644
--- a/runtimes/google/ipc/context_test.go
+++ b/runtimes/google/ipc/context_test.go
@@ -49,9 +49,44 @@
 	}
 }
 
+func TestRootContext(t *testing.T) {
+	r := &runtime.PanicRuntime{}
+	ctx := InternalNewContext(r)
+
+	if got := ctx.Runtime(); got != r {
+		t.Errorf("Expected runtime %v, but found %v", r, got)
+	}
+
+	if got := ctx.Err(); got != nil {
+		t.Errorf("Expected nil error, got: %v", got)
+	}
+
+	defer func() {
+		r := recover()
+		if r != nilRuntimeMessage {
+			t.Errorf("Unexpected recover value: %s", r)
+		}
+	}()
+	InternalNewContext(nil)
+}
+
 func TestCancelContext(t *testing.T) {
 	ctx, cancel := testContext().WithCancel()
 	testCancel(t, ctx, cancel)
+
+	// Test cancelling a cancel context which is the child
+	// of a cancellable context.
+	parent, _ := testContext().WithCancel()
+	child, cancel := parent.WithCancel()
+	cancel()
+	<-child.Done()
+
+	// Test adding a cancellable child context after the parent is
+	// already cancelled.
+	parent, cancel = testContext().WithCancel()
+	cancel()
+	child, _ = parent.WithCancel()
+	<-child.Done() // The child should have been cancelled right away.
 }
 
 func TestMultiLevelCancelContext(t *testing.T) {
@@ -80,11 +115,14 @@
 }
 
 func TestCancelContextWithNonStandard(t *testing.T) {
-	c0, c0Cancel := testContext().WithCancel()
-	c1 := &nonStandardContext{c0}
+	// Test that cancellation flows properly through non-standard intermediates.
+	ctx := testContext()
+	c0 := &nonStandardContext{ctx}
+	c1, c1Cancel := c0.WithCancel()
 	c2 := &nonStandardContext{c1}
-	c3, _ := c2.WithCancel()
-	testCancel(t, c3, c0Cancel)
+	c3 := &nonStandardContext{c2}
+	c4, _ := c3.WithCancel()
+	testCancel(t, c4, c1Cancel)
 }
 
 func testDeadline(t *testing.T, ctx context.T, start time.Time, desiredTimeout time.Duration) {
@@ -99,12 +137,37 @@
 
 func TestDeadlineContext(t *testing.T) {
 	cases := []time.Duration{
-		10 * time.Millisecond,
+		3 * time.Millisecond,
 		0,
 	}
+	rootCtx := InternalNewContext(&runtime.PanicRuntime{})
+	cancelCtx, _ := rootCtx.WithCancel()
+	deadlineCtx, _ := rootCtx.WithDeadline(time.Now().Add(time.Hour))
+
 	for _, desiredTimeout := range cases {
+		// Test all the various ways of getting deadline contexts.
 		start := time.Now()
-		ctx, _ := testContext().WithDeadline(start.Add(desiredTimeout))
+		ctx, _ := rootCtx.WithDeadline(start.Add(desiredTimeout))
+		testDeadline(t, ctx, start, desiredTimeout)
+
+		start = time.Now()
+		ctx, _ = cancelCtx.WithDeadline(start.Add(desiredTimeout))
+		testDeadline(t, ctx, start, desiredTimeout)
+
+		start = time.Now()
+		ctx, _ = deadlineCtx.WithDeadline(start.Add(desiredTimeout))
+		testDeadline(t, ctx, start, desiredTimeout)
+
+		start = time.Now()
+		ctx, _ = rootCtx.WithTimeout(desiredTimeout)
+		testDeadline(t, ctx, start, desiredTimeout)
+
+		start = time.Now()
+		ctx, _ = cancelCtx.WithTimeout(desiredTimeout)
+		testDeadline(t, ctx, start, desiredTimeout)
+
+		start = time.Now()
+		ctx, _ = deadlineCtx.WithTimeout(desiredTimeout)
 		testDeadline(t, ctx, start, desiredTimeout)
 	}
 
diff --git a/tools/mgmt/nminstall b/tools/mgmt/nminstall
index 858ade7..e6d71fd 100755
--- a/tools/mgmt/nminstall
+++ b/tools/mgmt/nminstall
@@ -163,8 +163,24 @@
   local -r NM_ROOT="${INSTALL_DIR}/nmroot"
   echo "Installing node manager under ${NM_ROOT} ..."
   local -r PUBLISH=$(hostname)
-  VEYRON_NM_CURRENT="${INSTALL_DIR}/curr" VEYRON_NM_ROOT="${NM_ROOT}" VEYRON_NM_HELPER="${SETUID_SCRIPT}" "${BIN_INSTALL}/noded" --install_self --name="${PUBLISH}"
+  VEYRON_NM_CURRENT="${INSTALL_DIR}/noded.curr" VEYRON_NM_ROOT="${NM_ROOT}" VEYRON_NM_HELPER="${SETUID_SCRIPT}" "${BIN_INSTALL}/noded" --install_self --name="${PUBLISH}"
   echo "Node manager installed."
+
+  local -r SECURITY_DIR="${INSTALL_DIR}/security"
+  mkdir -m 700 "${SECURITY_DIR}"
+  local -r PRINCIPAL_DIR="${SECURITY_DIR}/principal"
+  mkdir -m 700 "${PRINCIPAL_DIR}"
+  local -r AGENT_KEY_DIR="${SECURITY_DIR}/keys"
+  mkdir -m 700 "${AGENT_KEY_DIR}"
+
+  # Run node manager under the security agent.
+  echo
+  echo "Running:"
+  echo "VEYRON_CREDENTIALS=\"${PRINCIPAL_DIR}\" \"${BIN_INSTALL}/agentd\" --additional_principals=\"${AGENT_KEY_DIR}\" \"${INSTALL_DIR}/noded.curr\""
+  echo
+  # NOTE: If you update the command below, please also update the command echoed
+  # above to keep the two in sync.
+  VEYRON_CREDENTIALS="${PRINCIPAL_DIR}" "${BIN_INSTALL}/agentd" --additional_principals="${AGENT_KEY_DIR}" "${INSTALL_DIR}/noded.curr"
 }
 
 main "$@"
diff --git a/tools/mgmt/nodex/acl_impl.go b/tools/mgmt/nodex/acl_impl.go
new file mode 100644
index 0000000..8348f5b
--- /dev/null
+++ b/tools/mgmt/nodex/acl_impl.go
@@ -0,0 +1,81 @@
+package main
+
+// Commands to get/set ACLs.
+
+import (
+	"fmt"
+	"sort"
+
+	"veyron.io/veyron/veyron2/rt"
+	"veyron.io/veyron/veyron2/services/mgmt/node"
+
+	"veyron.io/veyron/veyron/lib/cmdline"
+)
+
+var cmdGet = &cmdline.Command{
+	Run:      runGet,
+	Name:     "get",
+	Short:    "Get ACLs for the given target.",
+	Long:     "Get ACLs for the given target with friendly output. Also see getraw.",
+	ArgsName: "<node manager name>",
+	ArgsLong: `
+<node manager name> can be a Vanadium name for a node manager,
+application installation or instance.`,
+}
+
+type formattedACLEntry struct {
+	blessing string
+	inout    string
+	label    string
+}
+
+// Code to make formattedACLEntry sorted.
+type byBlessing []formattedACLEntry
+
+func (a byBlessing) Len() int           { return len(a) }
+func (a byBlessing) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a byBlessing) Less(i, j int) bool { return a[i].blessing < a[j].blessing }
+
+func runGet(cmd *cmdline.Command, args []string) error {
+
+	if expected, got := 1, len(args); expected != got {
+		return cmd.UsageErrorf("install: incorrect number of arguments, expected %d, got %d", expected, got)
+	}
+
+	vanaName := args[0]
+	objACL, _, err := node.ApplicationClient(vanaName).GetACL(rt.R().NewContext())
+	if err != nil {
+		return fmt.Errorf("GetACL on %s failed: %v", vanaName, err)
+	}
+
+	// TODO(rjkroege): Update for custom labels.
+	output := make([]formattedACLEntry, 0)
+	for k, _ := range objACL.In {
+		output = append(output, formattedACLEntry{string(k), "in", objACL.In[k].String()})
+	}
+	for k, _ := range objACL.NotIn {
+
+		output = append(output, formattedACLEntry{string(k), "nin", objACL.NotIn[k].String()})
+	}
+
+	sort.Sort(byBlessing(output))
+
+	for _, e := range output {
+		fmt.Fprintf(cmd.Stdout(), "%s %s %s\n", e.blessing, e.inout, e.label)
+	}
+	return nil
+}
+
+// TODO(rjkroege): Implement the remaining sub-commands.
+// nodex acl set <target>  ([!]<label> <blessing>)...
+
+func aclRoot() *cmdline.Command {
+	return &cmdline.Command{
+		Name:  "acl",
+		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{cmdGet},
+	}
+}
diff --git a/tools/mgmt/nodex/acl_test.go b/tools/mgmt/nodex/acl_test.go
new file mode 100644
index 0000000..fb629b9
--- /dev/null
+++ b/tools/mgmt/nodex/acl_test.go
@@ -0,0 +1,56 @@
+package main
+
+import (
+	"bytes"
+	"reflect"
+	"strings"
+	"testing"
+
+	"veyron.io/veyron/veyron2/naming"
+	"veyron.io/veyron/veyron2/rt"
+	"veyron.io/veyron/veyron2/security"
+)
+
+func TestACLGetCommand(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{}{GetACLResponse{
+		acl: security.ACL{
+			In: map[security.BlessingPattern]security.LabelSet{
+				"root/self/...": security.AllLabels,
+				"root/other":    security.LabelSet(security.ReadLabel),
+			},
+			NotIn: map[string]security.LabelSet{
+				"root/bob/...": security.LabelSet(security.WriteLabel),
+			},
+		},
+		etag: "anEtagForToday",
+		err:  nil,
+	},
+	})
+
+	if err := cmd.Execute([]string{"acl", "get", nodeName}); err != nil {
+		t.Fatalf("%v, ouput: %v, error: %v", err)
+	}
+	if expected, got := "root/bob/... nin W\nroot/other in R\nroot/self/... in XRWADM", strings.TrimSpace(stdout.String()); got != expected {
+		t.Fatalf("Unexpected output from list. Got %q, expected %q", got, expected)
+	}
+	if got, expected := tape.Play(), []interface{}{"GetACL"}; !reflect.DeepEqual(expected, got) {
+		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
+	}
+	tape.Rewind()
+	stdout.Reset()
+}
diff --git a/tools/mgmt/nodex/impl.go b/tools/mgmt/nodex/impl.go
index e4547fc..e062da2 100644
--- a/tools/mgmt/nodex/impl.go
+++ b/tools/mgmt/nodex/impl.go
@@ -107,8 +107,6 @@
 		Long: `
 The nodex tool facilitates interaction with the veyron node manager.
 `,
-
-		// TODO(rjk): will have to fold these in...
-		Children: []*cmdline.Command{cmdInstall, cmdStart, associateRoot(), cmdClaim, cmdStop, cmdSuspend, cmdResume},
+		Children: []*cmdline.Command{cmdInstall, cmdStart, associateRoot(), cmdClaim, cmdStop, cmdSuspend, cmdResume, aclRoot()},
 	}
 }
diff --git a/tools/mgmt/nodex/impl_test.go b/tools/mgmt/nodex/impl_test.go
index f2111a0..031447d 100644
--- a/tools/mgmt/nodex/impl_test.go
+++ b/tools/mgmt/nodex/impl_test.go
@@ -83,21 +83,31 @@
 	return r.appId, r.err
 }
 
-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 (*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 }
+
+// Mock ACL getting and setting
+type GetACLResponse struct {
+	acl  security.ACL
+	etag string
+	err  error
 }
 
+func (mni *mockNodeInvoker) SetACL(ipc.ServerContext, security.ACL, string) error { return nil }
+func (mni *mockNodeInvoker) GetACL(ipc.ServerContext) (security.ACL, string, error) {
+	ir := mni.tape.Record("GetACL")
+	r := ir.(GetACLResponse)
+	return r.acl, r.etag, r.err
+}
+

 type dispatcher struct {
 	tape *Tape
 	t    *testing.T