tools/vrun: Add a tool to run a command with a derived principal under the agent.

Change-Id: Ie280f1b24f92fc849a42b0433aa0f838c3f1b16d
diff --git a/tools/vrun/doc.go b/tools/vrun/doc.go
new file mode 100644
index 0000000..7b24f38
--- /dev/null
+++ b/tools/vrun/doc.go
@@ -0,0 +1,41 @@
+/*
+The vrun tool executes a command with a derived principal.
+
+Usage:
+   vrun [flags] <command> [command args...]
+
+The vrun flags are:
+ -duration=1h0m0s
+   Duration for the blessing.
+ -name=
+   Name to use for the blessing. Uses the command name if unset.
+
+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
+ -veyron.credentials=
+   directory to use for storing security credentials
+ -veyron.namespace.root=[/proxy.envyor.com:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -veyron.vtrace.cache_size=1024
+   The number of vtrace traces to store in memory.
+ -veyron.vtrace.dump_on_shutdown=false
+   If true, dump all stored traces on runtime shutdown.
+ -veyron.vtrace.sample_rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for file-filtered logging
+*/
+package main
diff --git a/tools/vrun/test.sh b/tools/vrun/test.sh
new file mode 100755
index 0000000..005984c
--- /dev/null
+++ b/tools/vrun/test.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# Test running an application using vrun under the agent.
+
+source "${VEYRON_ROOT}/scripts/lib/shell_test.sh"
+
+readonly WORKDIR="${shell_test_WORK_DIR}"
+
+build() {
+  AGENTD_BIN="$(shell_test::build_go_binary 'veyron.io/veyron/veyron/security/agent/agentd')"
+}
+
+main() {
+  cd "${WORKDIR}"
+  build
+
+  export VEYRON_CREDENTIALS="$(shell::tmp_dir)"
+
+  # Make sure the testchild.sh script gets the same shell_test_BIN_DIR as the main script.
+  export shell_test_BIN_DIR
+  "${AGENTD_BIN}" --no_passphrase --additional_principals="$(shell::tmp_dir)" bash "${VEYRON_ROOT}/veyron/go/src/veyron.io/veyron/veyron/tools/vrun/testchild.sh" || shell_test::fail "${LINENO}: testchild.sh failed"
+
+  shell_test::pass
+}
+
+main "$@"
diff --git a/tools/vrun/testchild.sh b/tools/vrun/testchild.sh
new file mode 100644
index 0000000..5e098f4
--- /dev/null
+++ b/tools/vrun/testchild.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+
+# Helper script for testing vrun.
+
+source "${VEYRON_ROOT}/scripts/lib/shell_test.sh"
+
+main() {
+  shell_test::setup_server_test
+  local -r PINGPONG="$(shell_test::build_go_binary 'veyron.io/veyron/veyron/security/agent/pingpong')"
+  local -r VRUN="$(shell_test::build_go_binary 'veyron.io/veyron/veyron/tools/vrun')"
+  local -r PRINCIPAL="$(shell_test::build_go_binary 'veyron.io/veyron/veyron/tools/principal')"
+
+  local blessing=$("${PRINCIPAL}" dump | grep Default|cut -d: -f2)
+  shell_test::assert_eq "${blessing}" " agent_principal" $LINENO
+  blessing=$("${VRUN}" "${PRINCIPAL}" dump | grep Default|cut -d: -f2)
+  shell_test::assert_eq "${blessing}" " agent_principal/principal" $LINENO
+  blessing=$("${VRUN}" --name=foo "${PRINCIPAL}" dump | grep Default|cut -d: -f2)
+  shell_test::assert_eq "${blessing}" " agent_principal/foo" $LINENO
+
+  # TODO(ribrdb): test --duration
+
+  shell_test::start_server "${VRUN}" "${PINGPONG}" --server
+  "${VRUN}" "${PINGPONG}" || shell_test::fail "line ${LINENO}: ping"
+  shell_test::pass
+}
+
+main "$@"
diff --git a/tools/vrun/vrun.go b/tools/vrun/vrun.go
new file mode 100644
index 0000000..8fb8555
--- /dev/null
+++ b/tools/vrun/vrun.go
@@ -0,0 +1,148 @@
+package main
+
+import (
+	"flag"
+	"os"
+	"path/filepath"
+	"syscall"
+	"time"
+
+	"veyron.io/lib/cmdline"
+	"veyron.io/veyron/veyron/lib/flags/consts"
+	"veyron.io/veyron/veyron/security/agent"
+	"veyron.io/veyron/veyron/security/agent/keymgr"
+
+	"veyron.io/veyron/veyron2"
+	"veyron.io/veyron/veyron2/options"
+	"veyron.io/veyron/veyron2/rt"
+	"veyron.io/veyron/veyron2/security"
+	"veyron.io/veyron/veyron2/vlog"
+
+	_ "veyron.io/veyron/veyron/profiles"
+)
+
+var runtime veyron2.Runtime
+var durationFlag time.Duration
+var nameOverride string
+
+var cmdVrun = &cmdline.Command{
+	Run:      vrun,
+	Name:     "vrun",
+	Short:    "Executes a command with a derived principal.",
+	Long:     "The vrun tool executes a command with a derived principal.",
+	ArgsName: "<command> [command args...]",
+}
+
+func main() {
+	syscall.CloseOnExec(3)
+	syscall.CloseOnExec(4)
+
+	flag.DurationVar(&durationFlag, "duration", 1*time.Hour, "Duration for the blessing.")
+	flag.StringVar(&nameOverride, "name", "", "Name to use for the blessing. Uses the command name if unset.")
+	cmdVrun.Flags.DurationVar(&durationFlag, "duration", 1*time.Hour, "Duration for the blessing.")
+	cmdVrun.Flags.StringVar(&nameOverride, "name", "", "Name to use for the blessing. Uses the command name if unset.")
+
+	cmdVrun.Main()
+}
+
+func vrun(cmd *cmdline.Command, args []string) error {
+	var err error
+	runtime, err = rt.New()
+	if err != nil {
+		vlog.Errorf("Could not initialize runtime")
+		return err
+	}
+	defer runtime.Cleanup()
+
+	if len(args) == 0 {
+		return cmd.UsageErrorf("vrun: no command specified")
+	}
+	principal, conn, err := createPrincipal()
+	if err != nil {
+		return err
+	}
+	err = bless(principal, filepath.Base(args[0]))
+	if err != nil {
+		return err
+	}
+	return doExec(args, conn)
+}
+
+func bless(p security.Principal, name string) error {
+	caveat, err := security.ExpiryCaveat(time.Now().Add(durationFlag))
+	if err != nil {
+		vlog.Errorf("Couldn't create caveat")
+		return err
+	}
+	if 0 != len(nameOverride) {
+		name = nameOverride
+	}
+
+	blessing, err := runtime.Principal().Bless(p.PublicKey(), runtime.Principal().BlessingStore().Default(), name, caveat)
+	if err != nil {
+		vlog.Errorf("Couldn't bless")
+		return err
+	}
+
+	if err = p.BlessingStore().SetDefault(blessing); err != nil {
+		vlog.Errorf("Couldn't set default blessing")
+		return err
+	}
+	if _, err = p.BlessingStore().Set(blessing, security.AllPrincipals); err != nil {
+		vlog.Errorf("Couldn't set default client blessing")
+		return err
+	}
+	if err = p.AddToRoots(blessing); err != nil {
+		vlog.Errorf("Couldn't set trusted roots")
+		return err
+	}
+	return nil
+}
+
+func doExec(cmd []string, conn *os.File) error {
+	os.Setenv(consts.VeyronCredentials, "")
+	os.Setenv(agent.FdVarName, "3")
+	if conn.Fd() != 3 {
+		if err := syscall.Dup2(int(conn.Fd()), 3); err != nil {
+			vlog.Errorf("Couldn't dup fd")
+			return err
+		}
+		conn.Close()
+	}
+	err := syscall.Exec(cmd[0], cmd, os.Environ())
+	vlog.Errorf("Couldn't exec %s.", cmd[0])
+	return err
+}
+
+func createPrincipal() (security.Principal, *os.File, error) {
+	kagent, err := keymgr.NewAgent()
+	if err != nil {
+		vlog.Errorf("Could not initialize agent")
+		return nil, nil, err
+	}
+
+	_, conn, err := kagent.NewPrincipal(runtime.NewContext(), true)
+	if err != nil {
+		vlog.Errorf("Couldn't create principal")
+		return nil, nil, err
+	}
+
+	// Connect to the Principal
+	client, err := runtime.NewClient(options.VCSecurityNone)
+	if err != nil {
+		vlog.Errorf("Couldn't create client")
+		return nil, nil, err
+	}
+	fd, err := syscall.Dup(int(conn.Fd()))
+	if err != nil {
+		vlog.Errorf("Couldn't copy fd")
+		return nil, nil, err
+	}
+	syscall.CloseOnExec(fd)
+	principal, err := agent.NewAgentPrincipal(client, fd, runtime.NewContext())
+	if err != nil {
+		vlog.Errorf("Couldn't connect to principal")
+		return nil, nil, err
+	}
+	return principal, conn, nil
+}