playground/builder: use keymanager instead of on-disk credentials

Needed in order to be able to safely share the principal among several
processes.  With the change to lock credentials loaded from disk as part
of v.io/i/1214, sharing on-disk credentials among processes without
using an agent will be disallowed.

Change-Id: I25ef2e7ca195ec3adafe96bd8200bab6261f289c
diff --git a/go/src/v.io/x/playground/builder/credentials.go b/go/src/v.io/x/playground/builder/credentials.go
index 5177582..e8567df 100644
--- a/go/src/v.io/x/playground/builder/credentials.go
+++ b/go/src/v.io/x/playground/builder/credentials.go
@@ -7,11 +7,16 @@
 package main
 
 import (
-	"bytes"
 	"fmt"
-	"os"
-	"os/exec"
-	"path"
+	"path/filepath"
+	"sync"
+	"time"
+
+	"v.io/v23/security"
+	libsecurity "v.io/x/ref/lib/security"
+	"v.io/x/ref/services/agent"
+	"v.io/x/ref/services/agent/agentlib"
+	"v.io/x/ref/services/agent/keymgr"
 )
 
 type credentials struct {
@@ -27,113 +32,7 @@
 	defaultCredentials = "default"     // What codeFile.credentials defaults to if empty
 )
 
-var reservedCredentials = []string{identityProvider, "mounttabled", "proxyd", defaultCredentials}
-
-func (c credentials) create() error {
-	if err := c.init(); err != nil {
-		return err
-	}
-	if c.Blesser == "" && c.Duration != "" {
-		return c.initWithDuration()
-	}
-	if c.Blesser != "" {
-		return c.getblessed()
-	}
-	return nil
-}
-
-func (c credentials) init() error {
-	dir := path.Join(credentialsDir, c.Name)
-	if _, err := os.Stat(dir); os.IsNotExist(err) {
-		return c.toolCmd("", "create", dir, c.Name).Run()
-	}
-	return nil
-}
-
-func (c credentials) initWithDuration() error {
-	// (1) principal blessself --for=<duration> <c.Name> | principal set default -
-	// (2) principal get default | principal set forpeer - ...
-	if err := c.pipe(c.toolCmd(c.Name, "blessself", "--for", c.Duration),
-		c.toolCmd(c.Name, "set", "default", "-")); err != nil {
-		return err
-	}
-	if err := c.pipe(c.toolCmd(c.Name, "get", "default"),
-		c.toolCmd(c.Name, "set", "forpeer", "-", "...")); err != nil {
-		return err
-	}
-	return nil
-}
-
-func (c credentials) getblessed() error {
-	// (1) V23_CREDENTIALS=<c.Blesser> principal bless <c.Name> --for=<c.Duration> <c.Name> | V23_CREDENTIALS=<c.Name> principal set default -
-	// (2) principal get default | principal set forpeer - ...
-	duration := c.Duration
-	if duration == "" {
-		duration = "1h"
-	}
-	if err := c.pipe(c.toolCmd(c.Blesser, "bless", "--for", duration, path.Join(credentialsDir, c.Name), c.Name),
-		c.toolCmd(c.Name, "set", "default", "-")); err != nil {
-		return err
-	}
-	if err := c.pipe(c.toolCmd(c.Name, "get", "default"),
-		c.toolCmd(c.Name, "set", "forpeer", "-", "...")); err != nil {
-		return err
-	}
-	return nil
-}
-
-func (c credentials) pipe(from, to *exec.Cmd) error {
-	buf := new(bytes.Buffer)
-	from.Stdout = buf
-	to.Stdin = buf
-	if err := from.Run(); err != nil {
-		return fmt.Errorf("%v %v: %v", from.Path, from.Args, err)
-	}
-	if err := to.Run(); err != nil {
-		return fmt.Errorf("%v %v: %v", to.Path, to.Args, err)
-	}
-	return nil
-}
-
-func (c credentials) toolCmd(credentials string, args ...string) *exec.Cmd {
-	cmd := makeCmd("<principal>", false, credentials, "principal", args...)
-	// Set Stdout to /dev/null so that output does not leak into the
-	// playground output. If the output is needed, it can be overridden by
-	// clients of this method.
-	cmd.Stdout = nil
-	return cmd
-}
-
-func createCredentials(creds []credentials) error {
-	debug("Generating credentials")
-	for _, c := range creds {
-		if err := c.create(); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func baseCredentials() []credentials {
-	ret := []credentials{{Name: identityProvider}}
-	for _, name := range reservedCredentials {
-		if name != identityProvider {
-			ret = append(ret, credentials{Name: name, Blesser: identityProvider})
-		}
-	}
-	return ret
-}
-
-func rootCredentialsAtIdentityProvider(in []credentials) []credentials {
-	out := make([]credentials, len(in))
-	for idx, creds := range in {
-		if creds.Blesser == "" {
-			creds.Blesser = identityProvider
-		}
-		out[idx] = creds
-	}
-	return out
-}
+var reservedCredentials = []string{identityProvider, "mounttabled", "xproxyd", defaultCredentials}
 
 func isReservedCredential(name string) bool {
 	for _, c := range reservedCredentials {
@@ -143,3 +42,138 @@
 	}
 	return false
 }
+
+type credentialsManager struct {
+	sync.Mutex
+	dir    string
+	keyMgr agent.KeyManager
+	// Map from blessing to socket file path.
+	socks map[string]string
+}
+
+func (cm *credentialsManager) socket(name string) (string, error) {
+	cm.Lock()
+	defer cm.Unlock()
+	return cm.socketLocked(name)
+}
+
+// called with cm's lock held.
+func (cm *credentialsManager) socketLocked(name string) (string, error) {
+	sock, ok := cm.socks[name]
+	if !ok {
+		return "", fmt.Errorf("principal for blessing \"%s\" doesn't exist", name)
+	}
+	return sock, nil
+}
+
+func (cm *credentialsManager) principal(name string) (agent.Principal, error) {
+	cm.Lock()
+	defer cm.Unlock()
+	return cm.principalLocked(name)
+}
+
+// called with cm's lock held.
+func (cm *credentialsManager) principalLocked(name string) (agent.Principal, error) {
+	sock, err := cm.socketLocked(name)
+	if err != nil {
+		return nil, err
+	}
+	return agentlib.NewAgentPrincipal(sock, 0)
+}
+
+func (cm *credentialsManager) createPrincipal(blesser agent.Principal, name string, expiresAfter time.Duration) error {
+	cm.Lock()
+	defer cm.Unlock()
+	if _, ok := cm.socks[name]; ok {
+		return fmt.Errorf("principal with blessing name \"%s\" already exists", name)
+	}
+	handle, err := cm.keyMgr.NewPrincipal(true)
+	if err != nil {
+		return err
+	}
+	sockPath := filepath.Join(cm.dir, fmt.Sprintf("sock%d", len(cm.socks)))
+	if err := cm.keyMgr.ServePrincipal(handle, sockPath); err != nil {
+		return err
+	}
+	cm.socks[name] = sockPath
+	p, err := cm.principalLocked(name)
+	if err != nil {
+		return err
+	}
+	defer p.Close()
+	expiry, err := security.NewExpiryCaveat(time.Now().Add(expiresAfter))
+	if err != nil {
+		return err
+	}
+	var blessing security.Blessings
+	if blesser == nil {
+		// Self-blessed.
+		if blessing, err = p.BlessSelf(name, expiry); err != nil {
+			return err
+		}
+	} else {
+		with, _ := blesser.BlessingStore().Default()
+		if blessing, err = blesser.Bless(p.PublicKey(), with, name, expiry); err != nil {
+			return err
+		}
+	}
+	return libsecurity.SetDefaultBlessings(p, blessing)
+}
+
+func newCredentialsManager(creds []credentials) (*credentialsManager, error) {
+	keyMgr, err := keymgr.NewLocalAgent(credentialsDir, nil)
+	if err != nil {
+		return nil, err
+	}
+	credsMgr := &credentialsManager{
+		dir:    credentialsDir,
+		keyMgr: keyMgr,
+		socks:  make(map[string]string),
+	}
+	// Create the root identity provider.
+	if err := credsMgr.createPrincipal(nil, identityProvider, time.Hour); err != nil {
+		return nil, err
+	}
+	rootPrincipal, err := credsMgr.principal(identityProvider)
+	if err != nil {
+		return nil, err
+	}
+	defer rootPrincipal.Close()
+	// Create the other reserved principals.
+	for _, name := range reservedCredentials {
+		if name != identityProvider {
+			if err := credsMgr.createPrincipal(rootPrincipal, name, time.Hour); err != nil {
+				return nil, err
+			}
+		}
+	}
+	// Create all the user-specified principals.
+	for _, cred := range creds {
+		var blesser agent.Principal
+		if cred.Blesser == "" {
+			blesser = rootPrincipal
+		} else {
+			blesser, err = credsMgr.principal(cred.Blesser)
+			if err != nil {
+				return nil, err
+			}
+			defer blesser.Close()
+		}
+		expiry := time.Hour
+		if cred.Duration != "" {
+			expiry, err = time.ParseDuration(cred.Duration)
+			if err != nil {
+				return nil, err
+			}
+		}
+		if err := credsMgr.createPrincipal(blesser, cred.Name, expiry); err != nil {
+			return nil, err
+		}
+	}
+
+	return credsMgr, nil
+}
+
+func (cm *credentialsManager) Close() error {
+	return cm.keyMgr.Close()
+}
diff --git a/go/src/v.io/x/playground/builder/main.go b/go/src/v.io/x/playground/builder/main.go
index b562790..b7b1051 100644
--- a/go/src/v.io/x/playground/builder/main.go
+++ b/go/src/v.io/x/playground/builder/main.go
@@ -35,6 +35,7 @@
 	"syscall"
 	"time"
 
+	"v.io/x/lib/envvar"
 	"v.io/x/playground/lib"
 	"v.io/x/playground/lib/event"
 	"v.io/x/ref"
@@ -47,9 +48,10 @@
 	// TODO(ivanpi): Separate out mounttable and proxy timeouts. Add compile timeout. Revise default.
 	runTimeout = flag.Duration("runTimeout", 5*time.Second, "Time limit for running user code.")
 
-	stopped = false    // Whether we have stopped execution of running files.
-	out     event.Sink // Sink for writing events (debug and run output) to stdout as JSON, one event per line.
-	mu      sync.Mutex
+	stopped  = false    // Whether we have stopped execution of running files.
+	out      event.Sink // Sink for writing events (debug and run output) to stdout as JSON, one event per line.
+	mu       sync.Mutex
+	credsMgr *credentialsManager
 )
 
 // Type of data sent to the builder on stdin.  Input should contain Files.  We
@@ -101,7 +103,11 @@
 
 func logProfileEnv() error {
 	if *includeProfileEnv {
-		return makeCmd("<environment>", false, "", "jiri", "profile", "env").Run()
+		cmd, err := makeCmd("<environment>", false, "", "jiri", "profile", "env")
+		if err != nil {
+			return err
+		}
+		return cmd.Run()
 	}
 	return nil
 }
@@ -221,15 +227,18 @@
 	// itself.
 	if found["go"] {
 		debug("Generating VDL for Go and compiling Go")
-		err = makeCmd("<compile>", false, "",
-			"jiri", "go", "install", "./...").Run()
+		cmd, err := makeCmd("<compile>", false, "", "jiri", "go", "install", "./...")
+		if err != nil {
+			return false, err
+		}
+		err = cmd.Run()
 		if _, ok := err.(*exec.ExitError); ok {
 			return true, nil
 		} else if err != nil {
 			return false, err
 		}
 	}
-	if err = os.Chdir(pwd); err != nil {
+	if err := os.Chdir(pwd); err != nil {
 		return false, fmt.Errorf("Error returning to parent directory: %v", err)
 	}
 	return false, nil
@@ -307,7 +316,11 @@
 }
 
 func (f *codeFile) startGo() error {
-	f.cmd = makeCmd(f.Name, false, f.credentials, filepath.Join("bin", f.binaryName))
+	var err error
+	f.cmd, err = makeCmd(f.Name, false, f.credentials, filepath.Join("bin", f.binaryName))
+	if err != nil {
+		return err
+	}
 	return f.cmd.Start()
 }
 
@@ -364,12 +377,17 @@
 // Creates a cmd whose outputs (stdout and stderr) are streamed to stdout as
 // Event objects. If you want to watch the output streams yourself, add your
 // own writer(s) to the MultiWriter before starting the command.
-func makeCmd(fileName string, isService bool, credentials, progName string, args ...string) *exec.Cmd {
+func makeCmd(fileName string, isService bool, credentials, progName string, args ...string) (*exec.Cmd, error) {
 	cmd := exec.Command(progName, args...)
-	cmd.Env = os.Environ()
+	vars := envvar.VarsFromOS()
 	if credentials != "" {
-		cmd.Env = append(cmd.Env, fmt.Sprintf("%v=%s", ref.EnvCredentials, filepath.Join(credentialsDir, credentials)))
+		sock, err := credsMgr.socket(credentials)
+		if err != nil {
+			return nil, err
+		}
+		vars.Set(ref.EnvAgentPath, sock)
 	}
+	cmd.Env = vars.ToSlice()
 	stdout, stderr := lib.NewMultiWriter(), lib.NewMultiWriter()
 	prefix := ""
 	if isService {
@@ -380,13 +398,17 @@
 		stderr.Add(event.NewStreamWriter(out, fileName, prefix+"stderr"))
 	}
 	cmd.Stdout, cmd.Stderr = stdout, stderr
-	return cmd
+	return cmd, nil
 }
 
 func main() {
 	// Remove any association with other credentials, start from a clean
 	// slate.
 	ref.EnvClearCredentials()
+	// TODO(caprita): This should really be part of EnvClearCredentials.
+	if err := os.Unsetenv(ref.EnvAgentPath); err != nil {
+		panic(err)
+	}
 	flag.Parse()
 
 	out = event.NewJsonSink(os.Stdout, !*verbose)
@@ -394,20 +416,9 @@
 	r, err := parseRequest(os.Stdin)
 	panicOnError(err)
 
-	// Create a common "identity provider" that will bless each principal
-	// in this test (including mounttable, proxy, etc. that will be
-	// started).
-	//
-	// TODO(ivanpi,ashankar): Credential management in this playground has
-	// become very unwieldy. As of March 2015, ashankar@ just what was
-	// expedient to get some other changes through, but what is left is
-	// certainly hacky. If the plan is to move all this process management
-	// to the "modules" framework, then we should be able to leverage that
-	// to manage and set credentials for each subprocess. If not, will have
-	// to think of something else, but in any case, should clean this up!
-	panicOnError(createCredentials(
-		append(baseCredentials(),
-			rootCredentialsAtIdentityProvider(r.Credentials)...)))
+	credsMgr, err = newCredentialsManager(r.Credentials)
+	panicOnError(err)
+	defer credsMgr.Close()
 
 	mt, err := startMount(*runTimeout)
 	panicOnError(err)
diff --git a/go/src/v.io/x/playground/builder/services.go b/go/src/v.io/x/playground/builder/services.go
index 5b7ea70..36b84df 100644
--- a/go/src/v.io/x/playground/builder/services.go
+++ b/go/src/v.io/x/playground/builder/services.go
@@ -28,7 +28,7 @@
 // TODO(ivanpi): this is all implemented in veyron/lib/modules/core, you
 // may be able to use that directly.
 
-func makeServiceCmd(progName string, args ...string) *exec.Cmd {
+func makeServiceCmd(progName string, args ...string) (*exec.Cmd, error) {
 	return makeCmd(fmt.Sprintf("<%v>", progName), true, progName, progName, args...)
 }
 
@@ -36,7 +36,10 @@
 // variable to the mounttable's location.  We run one mounttabled process for
 // the entire environment.
 func startMount(timeLimit time.Duration) (proc *os.Process, err error) {
-	cmd := makeServiceCmd("mounttabled", "-v23.tcp.address=127.0.0.1:0")
+	cmd, err := makeServiceCmd("mounttabled", "-v23.tcp.address=127.0.0.1:0")
+	if err != nil {
+		return nil, err
+	}
 	matches, err := startAndWaitFor(cmd, timeLimit, regexp.MustCompile("NAME=(.*)"))
 	if err != nil {
 		return nil, fmt.Errorf("Error starting mounttabled: %v", err)
@@ -51,12 +54,15 @@
 // startProxy starts a proxyd process.  We run one proxyd process for the
 // entire environment.
 func startProxy(timeLimit time.Duration) (proc *os.Process, err error) {
-	cmd := makeServiceCmd(
+	cmd, err := makeServiceCmd(
 		"xproxyd",
 		"-log_dir=/tmp/logs",
 		"-name="+proxyName,
 		"-access-list", fmt.Sprintf("{\"In\":[\"%v\"]}", identityProvider),
 		"-v23.tcp.address=127.0.0.1:0")
+	if err != nil {
+		return nil, err
+	}
 	if _, err := startAndWaitFor(cmd, timeLimit, regexp.MustCompile("NAME=(.*)")); err != nil {
 		return nil, fmt.Errorf("Error starting proxy: %v", err)
 	}