Merge "services/agent: Add a vbecome command to run with derived principals."
diff --git a/services/agent/vbecome/doc.go b/services/agent/vbecome/doc.go
new file mode 100644
index 0000000..02e0b6d
--- /dev/null
+++ b/services/agent/vbecome/doc.go
@@ -0,0 +1,66 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+
+/*
+Command vbecome executes commands with a derived Vanadium principal.
+
+Usage:
+ vbecome [flags] <command> [command args...]
+
+The vbecome flags are:
+ -duration=1h0m0s
+ Duration for the blessing.
+ -name=
+ Name to use for the blessing.
+ -role=
+ Role object from which to request the blessing. If set, the blessings from
+ this role server are used and --name is ignored. If not set, the default
+ blessings of the calling principal are extended with --name.
+
+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
+ -v23.credentials=
+ directory to use for storing security credentials
+ -v23.i18n-catalogue=
+ 18n catalogue files to load, comma separated
+ -v23.metadata=<just specify -v23.metadata to activate>
+ Displays metadata for the program and exits.
+ -v23.namespace.root=[/(dev.v.io/role/vprod/service/mounttabled)@ns.dev.v.io:8101]
+ local namespace root; can be repeated to provided multiple roots
+ -v23.proxy=
+ object name of proxy service to use to export services across network
+ boundaries
+ -v23.tcp.address=
+ address to listen on
+ -v23.tcp.protocol=wsh
+ protocol to listen with
+ -v23.vtrace.cache-size=1024
+ The number of vtrace traces to store in memory.
+ -v23.vtrace.collect-regexp=
+ Spans and annotations that match this regular expression will trigger trace
+ collection.
+ -v23.vtrace.dump-on-shutdown=true
+ If true, dump all stored traces on runtime shutdown.
+ -v23.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/services/agent/vbecome/v23_test.go b/services/agent/vbecome/v23_test.go
new file mode 100644
index 0000000..35ff567
--- /dev/null
+++ b/services/agent/vbecome/v23_test.go
@@ -0,0 +1,29 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+package main_test
+
+import "testing"
+import "os"
+
+import "v.io/x/ref/test"
+import "v.io/x/ref/test/v23tests"
+
+func TestMain(m *testing.M) {
+ test.Init()
+ cleanup := v23tests.UseSharedBinDir()
+ r := m.Run()
+ cleanup()
+ os.Exit(r)
+}
+
+func TestV23BecomeRole(t *testing.T) {
+ v23tests.RunTest(t, V23TestBecomeRole)
+}
+
+func TestV23BecomeName(t *testing.T) {
+ v23tests.RunTest(t, V23TestBecomeName)
+}
diff --git a/services/agent/vbecome/vbecome.go b/services/agent/vbecome/vbecome.go
new file mode 100644
index 0000000..929d6d1
--- /dev/null
+++ b/services/agent/vbecome/vbecome.go
@@ -0,0 +1,179 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The following enables go generate to generate the doc.go file.
+//go:generate go run $V23_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go . -help
+
+package main
+
+import (
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "syscall"
+ "time"
+
+ "v.io/v23"
+ "v.io/v23/context"
+ "v.io/v23/security"
+ "v.io/x/lib/cmdline"
+ "v.io/x/lib/vlog"
+ "v.io/x/ref/envvar"
+ vsecurity "v.io/x/ref/lib/security"
+ "v.io/x/ref/services/agent/internal/server"
+ "v.io/x/ref/services/role"
+
+ _ "v.io/x/ref/profiles"
+)
+
+var (
+ durationFlag time.Duration
+ nameFlag string
+ roleFlag string
+)
+
+var cmdVbecome = &cmdline.Command{
+ Run: vbecome,
+ Name: "vbecome",
+ Short: "executes commands with a derived Vanadium principal",
+ Long: "Command vbecome executes commands with a derived Vanadium principal.",
+ ArgsName: "<command> [command args...]",
+}
+
+const childAgentFd = 3
+const keyServerFd = 4
+
+func main() {
+ cmdline.HideGlobalFlagsExcept()
+ syscall.CloseOnExec(childAgentFd)
+ syscall.CloseOnExec(keyServerFd)
+
+ cmdVbecome.Flags.DurationVar(&durationFlag, "duration", 1*time.Hour, "Duration for the blessing.")
+ cmdVbecome.Flags.StringVar(&nameFlag, "name", "", "Name to use for the blessing.")
+ cmdVbecome.Flags.StringVar(&roleFlag, "role", "", "Role object from which to request the blessing. If set, the blessings from this role server are used and --name is ignored. If not set, the default blessings of the calling principal are extended with --name.")
+
+ os.Exit(cmdVbecome.Main())
+}
+
+func vbecome(cmd *cmdline.Command, args []string) error {
+ ctx, shutdown := v23.Init()
+ defer shutdown()
+
+ if len(args) == 0 {
+ if shell := os.Getenv("SHELL"); shell != "" {
+ args = []string{shell}
+ } else {
+ return fmt.Errorf("You must specify a command to run.")
+ }
+ }
+
+ key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ return err
+ }
+ signer := security.NewInMemoryECDSASigner(key)
+ principal, err := vsecurity.NewPrincipalFromSigner(signer, nil)
+ if err != nil {
+ return err
+ }
+
+ if len(roleFlag) == 0 {
+ if len(nameFlag) == 0 {
+ nameFlag = filepath.Base(args[0])
+ }
+ if err := bless(ctx, principal, nameFlag); err != nil {
+ return err
+ }
+ ctx, err = v23.WithPrincipal(ctx, principal)
+ if err != nil {
+ return err
+ }
+ } else {
+ // The role server expects the client's blessing name to end
+ // with RoleSuffix. This is to avoid accidentally granting role
+ // access to anything else that might have been blessed by the
+ // same principal.
+ if err := bless(ctx, principal, role.RoleSuffix); err != nil {
+ return err
+ }
+ ctx, err = v23.WithPrincipal(ctx, principal)
+ if err != nil {
+ return err
+ }
+ if err = setupRoleBlessings(ctx, roleFlag); err != nil {
+ return err
+ }
+ }
+
+ // Start an agent server.
+ sock, endpoint, err := server.RunAnonymousAgent(ctx, principal, childAgentFd)
+ if err != nil {
+ return err
+ }
+ if err = os.Setenv(envvar.AgentEndpoint, endpoint); err != nil {
+ vlog.Fatalf("setenv: %v", err)
+ }
+
+ return doExec(args, sock)
+}
+
+func bless(ctx *context.T, p security.Principal, name string) error {
+ caveat, err := security.NewExpiryCaveat(time.Now().Add(durationFlag))
+ if err != nil {
+ vlog.Errorf("Couldn't create caveat")
+ return err
+ }
+
+ rp := v23.GetPrincipal(ctx)
+ blessing, err := rp.Bless(p.PublicKey(), rp.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(args []string, sock *os.File) error {
+ cmd := exec.Command(args[0], args[1:]...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.ExtraFiles = []*os.File{sock}
+ return cmd.Run()
+}
+
+func setupRoleBlessings(ctx *context.T, roleStr string) error {
+ b, err := role.RoleClient(roleStr).SeekBlessings(ctx)
+ if err != nil {
+ return err
+ }
+ p := v23.GetPrincipal(ctx)
+ // TODO(rthellend,ashankar): Revisit this configuration.
+ // SetDefault: Should we expect users to want to act as a server on behalf of the role (by default?)
+ // AllPrincipals: Do we not want to be discriminating about which services we use the role blessing at.
+ if err := p.BlessingStore().SetDefault(b); err != nil {
+ return err
+ }
+ if _, err := p.BlessingStore().Set(b, security.AllPrincipals); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/services/agent/vbecome/vbecome_v23_test.go b/services/agent/vbecome/vbecome_v23_test.go
new file mode 100644
index 0000000..298af90
--- /dev/null
+++ b/services/agent/vbecome/vbecome_v23_test.go
@@ -0,0 +1,67 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main_test
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "regexp"
+
+ _ "v.io/x/ref/profiles"
+ "v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate .
+
+func writeRoledConfig() (path string, shutdown func(), err error) {
+ dir, err := ioutil.TempDir("", "")
+ if err != nil {
+ return dir, nil, err
+ }
+ err = ioutil.WriteFile(filepath.Join(dir, "therole.conf"), []byte(`
+{
+ "Members": ["root/child"],
+ "Extend": true
+}
+`), 0644)
+ return dir, func() { os.RemoveAll(dir) }, err
+}
+
+func V23TestBecomeRole(t *v23tests.T) {
+ vbecome := t.BuildV23Pkg("v.io/x/ref/services/agent/vbecome")
+ principal := t.BuildV23Pkg("v.io/x/ref/cmd/principal")
+
+ roled := t.BuildV23Pkg("v.io/x/ref/services/role/roled")
+ roledCreds, _ := t.Shell().NewChildCredentials("master")
+ roled = roled.WithStartOpts(roled.StartOpts().WithCustomCredentials(roledCreds))
+
+ v23tests.RunRootMT(t, "--v23.tcp.address=127.0.0.1:0")
+
+ dir, shutdown, err := writeRoledConfig()
+ if err != nil {
+ t.Fatalf("Couldn't write roled config: %v", err)
+ }
+ defer shutdown()
+ roled.Start("--v23.tcp.address=127.0.0.1:0", "--config-dir", dir, "--name", "roled")
+
+ output := vbecome.Run("--role=roled/therole", principal.Path(), "dump")
+ want := regexp.MustCompile(`Default Blessings\s+root/master/therole/root/child`)
+ if !want.MatchString(output) {
+ t.Errorf("Principal didn't have the role blessing:\n %s", output)
+ }
+}
+
+func V23TestBecomeName(t *v23tests.T) {
+ vbecome := t.BuildV23Pkg("v.io/x/ref/services/agent/vbecome")
+ principal := t.BuildV23Pkg("v.io/x/ref/cmd/principal")
+
+ v23tests.RunRootMT(t, "--v23.tcp.address=127.0.0.1:0")
+ output := vbecome.Run("--name=bob", principal.Path(), "dump")
+ want := regexp.MustCompile(`Default Blessings\s+root/child/bob`)
+ if !want.MatchString(output) {
+ t.Errorf("Principal didn't have the expected blessing:\n %s", output)
+ }
+}