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