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
+}