Revert "TBR: Revert "IPC implementation of agentd. The client library supports both IPC and RPC for now.""
This reverts commit 8b59ca39c5f4b98d07a4f7473fdd6e1d585cd275.
Change-Id: Ia5ce2249229955fc6ced331cacdf4181b847c905
diff --git a/cmd/vrun/vrun.go b/cmd/vrun/vrun.go
index e493884..8d06d39 100644
--- a/cmd/vrun/vrun.go
+++ b/cmd/vrun/vrun.go
@@ -8,6 +8,7 @@
package main
import (
+ "io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -21,6 +22,7 @@
"v.io/x/lib/vlog"
"v.io/x/ref"
"v.io/x/ref/lib/v23cmd"
+ "v.io/x/ref/services/agent"
"v.io/x/ref/services/agent/agentlib"
"v.io/x/ref/services/agent/keymgr"
"v.io/x/ref/services/role"
@@ -58,10 +60,22 @@
if len(args) == 0 {
args = []string{"bash", "--norc"}
}
- principal, conn, err := createPrincipal(ctx, env)
+ m, err := connectToKeyManager()
if err != nil {
- return env.UsageErrorf("%v", err)
+ return err
}
+
+ path, err := newPrincipal(m)
+ if err != nil {
+ return err
+ }
+
+ // Connect to the Principal
+ principal, err := agentlib.NewAgentPrincipalX(path)
+ if err != nil {
+ vlog.Errorf("Couldn't connect to principal")
+ }
+
if len(roleFlag) == 0 {
if len(nameFlag) == 0 {
nameFlag = filepath.Base(args[0])
@@ -86,7 +100,7 @@
}
}
- return doExec(args, conn)
+ return doExec(args, path)
}
func bless(ctx *context.T, p security.Principal, name string) error {
@@ -118,13 +132,10 @@
return nil
}
-func doExec(cmd []string, conn *os.File) error {
- if conn.Fd() != 3 {
- if err := syscall.Dup2(int(conn.Fd()), 3); err != nil {
- vlog.Errorf("Couldn't dup fd")
- return err
- }
- conn.Close()
+func doExec(cmd []string, agentPath string) error {
+ ref.EnvClearCredentials()
+ if err := os.Setenv(ref.EnvAgentPath, agentPath); err != nil {
+ return err
}
p, err := exec.LookPath(cmd[0])
if err != nil {
@@ -136,41 +147,26 @@
return err
}
-func createPrincipal(ctx *context.T, env *cmdline.Env) (security.Principal, *os.File, error) {
- kagent, err := keymgr.NewAgent()
- if err != nil {
- vlog.Errorf("Could not initialize agent")
- return nil, nil, err
- }
+func connectToKeyManager() (agent.KeyManager, error) {
+ path := os.Getenv(ref.EnvAgentPath)
+ return keymgr.NewKeyManager(path)
+}
- _, conn, err := kagent.NewPrincipal(ctx, true)
- if err != nil {
+func newPrincipal(m agent.KeyManager) (path string, err error) {
+ var dir string
+ if dir, err = ioutil.TempDir("", "vrun"); err != nil {
+ return
+ }
+ var id [64]byte
+ if id, err = m.NewPrincipal(true); err != nil {
vlog.Errorf("Couldn't create principal")
- return nil, nil, err
+ return
}
-
- ep, err := v23.NewEndpoint(env.Vars[ref.EnvAgentEndpoint])
- if err != nil {
- vlog.Errorf("Couldn't parse %v=%q: %v", ref.EnvAgentEndpoint, env.Vars[ref.EnvAgentEndpoint], err)
- return nil, nil, err
- }
- // Connect to the Principal
- fd, err := syscall.Dup(int(conn.Fd()))
- if err != nil {
- vlog.Errorf("Couldn't copy fd")
- return nil, nil, err
- }
- syscall.CloseOnExec(fd)
- ep, err = v23.NewEndpoint(agentlib.AgentEndpoint(fd))
- if err != nil {
- vlog.Errorf("Error creating endpoint: %v", err)
- return nil, nil, err
- }
- principal, err := agentlib.NewAgentPrincipal(ctx, ep, v23.GetClient(ctx))
- if err != nil {
- vlog.Errorf("Couldn't connect to principal")
- }
- return principal, conn, nil
+ // Note: because we exec the child, there's no way to cleanup
+ // this principal and socket after the child is gone.
+ path = filepath.Join(dir, "sock")
+ err = m.ServePrincipal(id, path)
+ return
}
func setupRoleBlessings(ctx *context.T, roleStr string) error {
diff --git a/envvar.go b/envvar.go
index bb7af0a..0c153db 100644
--- a/envvar.go
+++ b/envvar.go
@@ -24,11 +24,11 @@
// See v.io/x/ref/lib/security.CreatePersistentPrincipal.
EnvCredentials = "V23_CREDENTIALS"
- // EnvAgentPath is the name of the environment variable pointing to an
- // agentd process containing all the credentials a principal (the blessing
- // store, the blessing roots, possibly the private key etc.).
+ // EnvAgentPath is the name of the environment variable pointing to a socket
+ // of the agentd process containing all the credentials for a principal (the
+ // blessing store, the blessing roots, possibly the private key etc.).
//
- // Typically only one of EnvCredentials or EnvAgentPaths will be set in a
+ // Typically only one of EnvCredentials or EnvAgentPath will be set in a
// process. If both are set, then EnvCredentials takes preference.
EnvAgentPath = "V23_AGENT_PATH"
diff --git a/services/agent/agentd/doc.go b/services/agent/agentd/doc.go
index 787386f..22f3860 100644
--- a/services/agent/agentd/doc.go
+++ b/services/agent/agentd/doc.go
@@ -9,9 +9,9 @@
Command agentd runs the security agent daemon, which holds a private key in
memory and makes it available to a subprocess.
-Loads the private key specified in privatekey.pem in V23_CREDENTIALS into
-memory, then starts the specified command with access to the private key via the
-agent protocol instead of directly reading from disk.
+Loads the private key specified in privatekey.pem in the specified credentials
+directory into memory, then starts the specified command with access to the
+private key via the agent protocol instead of directly reading from disk.
Usage:
agentd [flags] command [command_args...]
@@ -32,6 +32,9 @@
command's exit code matches the value of this flag. The value must be an
integer, or an integer preceded by '!' (in which case all exit codes except
the flag will trigger a restart).
+ -v23.credentials=
+ The directory containing the (possibly encrypted) credentials to serve. Must
+ be specified.
The global flags are:
-alsologtostderr=true
@@ -50,28 +53,6 @@
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.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 filename-filtered logging
-vpath=
diff --git a/services/agent/agentd/main.go b/services/agent/agentd/main.go
index 16fc91e..dd7a657 100644
--- a/services/agent/agentd/main.go
+++ b/services/agent/agentd/main.go
@@ -14,12 +14,12 @@
"os"
"os/exec"
"os/signal"
+ "path/filepath"
"strconv"
"syscall"
"golang.org/x/crypto/ssh/terminal"
- "v.io/v23"
"v.io/v23/security"
"v.io/v23/verror"
"v.io/x/lib/cmdline"
@@ -27,20 +27,23 @@
"v.io/x/ref/internal/logger"
vsecurity "v.io/x/ref/lib/security"
vsignals "v.io/x/ref/lib/signals"
- _ "v.io/x/ref/runtime/factories/generic"
+ "v.io/x/ref/services/agent/internal/ipc"
+ "v.io/x/ref/services/agent/internal/lockfile"
"v.io/x/ref/services/agent/internal/server"
)
const childAgentFd = 3
const pkgPath = "v.io/x/ref/services/agent/agentd"
+const agentSocketName = "agent.sock" // Keep in sync with internal/lockfile/lockfile.go
var (
errCantReadPassphrase = verror.Register(pkgPath+".errCantReadPassphrase", verror.NoRetry, "{1:}{2:} failed to read passphrase{:_}")
errNeedPassphrase = verror.Register(pkgPath+".errNeedPassphrase", verror.NoRetry, "{1:}{2:} Passphrase required for decrypting principal{:_}")
errCantParseRestartExitCode = verror.Register(pkgPath+".errCantParseRestartExitCode", verror.NoRetry, "{1:}{2:} Failed to parse restart exit code{:_}")
- keypath, restartExitCode, newname string
- noPassphrase bool
+ keypath, restartExitCode string
+ newname, credentials string
+ noPassphrase bool
)
func main() {
@@ -54,6 +57,8 @@
cmdAgentD.Flags.StringVar(&newname, "new-principal-blessing-name", "", "If creating a new principal (--v23.credentials does not exist), then have it blessed with this name.")
+ cmdAgentD.Flags.StringVar(&credentials, "v23.credentials", "", "The directory containing the (possibly encrypted) credentials to serve. Must be specified.")
+
cmdline.HideGlobalFlagsExcept()
cmdline.Main(cmdAgentD)
}
@@ -62,14 +67,15 @@
Runner: cmdline.RunnerFunc(runAgentD),
Name: "agentd",
Short: "Holds a private key in memory and makes it available to a subprocess",
- Long: fmt.Sprintf(`
+ Long: `
Command agentd runs the security agent daemon, which holds a private key in
memory and makes it available to a subprocess.
-Loads the private key specified in privatekey.pem in %v into memory, then
-starts the specified command with access to the private key via the
-agent protocol instead of directly reading from disk.
-`, ref.EnvCredentials),
+Loads the private key specified in privatekey.pem in the specified
+credentials directory into memory, then starts the specified command
+with access to the private key via the agent protocol instead of
+directly reading from disk.
+`,
ArgsName: "command [command_args...]",
ArgsLong: `
The command is started as a subprocess with the given [command_args...].
@@ -85,38 +91,18 @@
return env.UsageErrorf("%v", err)
}
- // This is a bit tricky. We're trying to share the runtime's
- // v23.credentials flag. However we need to parse it before
- // creating the runtime. We depend on the profile's init() function
- // calling flags.CreateAndRegister(flag.CommandLine, flags.Runtime)
- // This will read the ref.EnvCredentials env var, then our call to
- // flag.Parse() will take any override passed on the command line.
- var dir string
- if f := flag.Lookup("v23.credentials").Value; true {
- dir = f.String()
- // Clear out the flag value to prevent v23.Init from
- // trying to load this password protected principal.
- f.Set("")
+ if len(credentials) == 0 {
+ credentials = os.Getenv(ref.EnvCredentials)
}
- if len(dir) == 0 {
- return env.UsageErrorf("The %v environment variable must be set to a directory: %q", ref.EnvCredentials, env.Vars[ref.EnvCredentials])
+ if len(credentials) == 0 {
+ return env.UsageErrorf("The -credentials flag must be specified.")
}
- p, passphrase, err := newPrincipalFromDir(dir)
+ p, passphrase, err := newPrincipalFromDir(credentials)
if err != nil {
- return fmt.Errorf("failed to create new principal from dir(%s): %v", dir, err)
+ return fmt.Errorf("failed to create new principal from dir(%s): %v", credentials, err)
}
-
- // Clear out the environment variable before v23.Init.
- if err = ref.EnvClearCredentials(); err != nil {
- return fmt.Errorf("ref.EnvClearCredentials: %v", err)
- }
- ctx, shutdown := v23.Init()
- defer shutdown()
-
- if ctx, err = v23.WithPrincipal(ctx, p); err != nil {
- return fmt.Errorf("failed to set principal for ctx: %v", err)
- }
+ defer lockfile.RemoveLockfile(credentials)
if keypath == "" && passphrase != nil {
// If we're done with the passphrase, zero it out so it doesn't stay in memory
@@ -127,19 +113,31 @@
}
// Start running our server.
- var sock, mgrSock *os.File
- var endpoint string
- if sock, endpoint, err = server.RunAnonymousAgent(ctx, p, childAgentFd); err != nil {
- return fmt.Errorf("RunAnonymousAgent: %v", err)
+ i := ipc.NewIPC()
+ defer i.Close()
+ if err = server.ServeAgent(i, p); err != nil {
+ return fmt.Errorf("ServeAgent: %v", err)
}
- if err = os.Setenv(ref.EnvAgentEndpoint, endpoint); err != nil {
+ if keypath != "" {
+ if err = server.ServeKeyManager(i, keypath, passphrase); err != nil {
+ return fmt.Errorf("ServeKeyManager: %v", err)
+ }
+ }
+ path, err := filepath.Abs(filepath.Join(credentials, agentSocketName))
+ if err != nil {
+ return fmt.Errorf("abs: %v", err)
+ }
+ path = filepath.Clean(path)
+ if err = os.Setenv(ref.EnvAgentPath, path); err != nil {
return fmt.Errorf("setenv: %v", err)
}
+ if err = i.Listen(path); err != nil {
+ return err
+ }
- if keypath != "" {
- if mgrSock, err = server.RunKeyManager(ctx, keypath, passphrase); err != nil {
- return fmt.Errorf("RunKeyManager: %v", err)
- }
+ // Clear out the environment variable before starting the child.
+ if err = ref.EnvClearCredentials(); err != nil {
+ return fmt.Errorf("ref.EnvClearCredentials: %v", err)
}
exitCode := 0
@@ -149,11 +147,6 @@
cmd.Stdin = env.Stdin
cmd.Stdout = env.Stdout
cmd.Stderr = env.Stderr
- cmd.ExtraFiles = []*os.File{sock}
-
- if mgrSock != nil {
- cmd.ExtraFiles = append(cmd.ExtraFiles, mgrSock)
- }
err = cmd.Start()
if err != nil {
@@ -162,7 +155,7 @@
shutdown := make(chan struct{})
go func() {
select {
- case sig := <-vsignals.ShutdownOnSignals(ctx):
+ case sig := <-vsignals.ShutdownOnSignals(nil):
// TODO(caprita): Should we also relay double
// signal to the child? That currently just
// force exits the current process.
@@ -180,18 +173,19 @@
break
}
}
- // TODO(caprita): If restartOpts.enabled is false, we could close these
- // right after cmd.Start().
- sock.Close()
- mgrSock.Close()
if exitCode != 0 {
return cmdline.ErrExitCode(exitCode)
}
return nil
}
-func newPrincipalFromDir(dir string) (security.Principal, []byte, error) {
- p, err := vsecurity.LoadPersistentPrincipal(dir, nil)
+func newPrincipalFromDir(dir string) (p security.Principal, pass []byte, err error) {
+ defer func() {
+ if err == nil {
+ err = lockfile.CreateLockfile(dir)
+ }
+ }()
+ p, err = vsecurity.LoadPersistentPrincipal(dir, nil)
if os.IsNotExist(err) {
return handleDoesNotExist(dir)
}
diff --git a/services/agent/agentlib/agent_test.go b/services/agent/agentlib/agent_test.go
index 46170bf..03d07fa 100644
--- a/services/agent/agentlib/agent_test.go
+++ b/services/agent/agentlib/agent_test.go
@@ -8,14 +8,15 @@
"fmt"
"io/ioutil"
"os"
+ "path/filepath"
"reflect"
"testing"
"time"
"v.io/v23"
- "v.io/v23/context"
"v.io/v23/security"
"v.io/x/ref/services/agent/agentlib"
+ "v.io/x/ref/services/agent/internal/ipc"
"v.io/x/ref/services/agent/internal/server"
"v.io/x/ref/test"
"v.io/x/ref/test/modules"
@@ -52,58 +53,70 @@
return nil
}, "getPrincipalAndHang")
-func newAgent(ctx *context.T, endpoint string, cached bool) (security.Principal, error) {
- ep, err := v23.NewEndpoint(endpoint)
- if err != nil {
- return nil, err
- }
+func newAgent(path string, cached bool) (security.Principal, error) {
if cached {
- return agentlib.NewAgentPrincipal(ctx, ep, v23.GetClient(ctx))
+ return agentlib.NewAgentPrincipalX(path)
} else {
- return agentlib.NewUncachedPrincipal(ctx, ep, v23.GetClient(ctx))
+ return agentlib.NewUncachedPrincipalX(path)
}
}
-func setupAgentPair(t *testing.T, ctx *context.T, p security.Principal) (security.Principal, security.Principal) {
- sock, ep, err := server.RunAnonymousAgent(ctx, p, -1)
+func setupAgentPair(t *testing.T, p security.Principal) (security.Principal, security.Principal, func()) {
+ i := ipc.NewIPC()
+ if err := server.ServeAgent(i, p); err != nil {
+ t.Fatal(err)
+ }
+ dir, err := ioutil.TempDir("", "agent")
if err != nil {
t.Fatal(err)
}
- defer sock.Close()
-
- agent1, err := newAgent(ctx, ep, true)
+ sock := filepath.Join(dir, "sock")
+ defer os.RemoveAll(dir)
+ if err := i.Listen(sock); err != nil {
+ t.Fatal(err)
+ }
+ agent1, err := newAgent(sock, true)
if err != nil {
t.Fatal(err)
}
- agent2, err := newAgent(ctx, ep, true)
+ agent2, err := newAgent(sock, true)
if err != nil {
t.Fatal(err)
}
- return agent1, agent2
+ return agent1, agent2, i.Close
}
-func setupAgent(ctx *context.T, caching bool) security.Principal {
- sock, ep, err := server.RunAnonymousAgent(ctx, v23.GetPrincipal(ctx), -1)
+func setupAgent(caching bool) (security.Principal, func()) {
+ p := testutil.NewPrincipal("agentTest")
+ i := ipc.NewIPC()
+ if err := server.ServeAgent(i, p); err != nil {
+ panic(err)
+ }
+ dir, err := ioutil.TempDir("", "agent")
if err != nil {
panic(err)
}
- defer sock.Close()
- agent, err := newAgent(ctx, ep, caching)
+ sock := filepath.Join(dir, "sock")
+ defer os.RemoveAll(dir)
+ if err := i.Listen(sock); err != nil {
+ panic(err)
+ }
+
+ agent, err := newAgent(sock, caching)
if err != nil {
panic(err)
}
- return agent
+ return agent, i.Close
}
func TestAgent(t *testing.T) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
var (
- p = testutil.NewPrincipal("agentTest")
- agent1, agent2 = setupAgentPair(t, ctx, p)
+ p = testutil.NewPrincipal("agentTest")
+ agent1, agent2, cleanup = setupAgentPair(t, p)
)
+ defer cleanup()
defP, def1, def2 := p.BlessingStore().Default(), agent1.BlessingStore().Default(), agent2.BlessingStore().Default()
@@ -234,73 +247,69 @@
}
func BenchmarkSignNoAgent(b *testing.B) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
- runSignBenchmark(b, v23.GetPrincipal(ctx))
+ p := testutil.NewPrincipal("agentTest")
+ runSignBenchmark(b, p)
}
func BenchmarkSignCachedAgent(b *testing.B) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
- runSignBenchmark(b, setupAgent(ctx, true))
+ p, cleanup := setupAgent(true)
+ defer cleanup()
+ runSignBenchmark(b, p)
}
func BenchmarkSignUncachedAgent(b *testing.B) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
- runSignBenchmark(b, setupAgent(ctx, false))
+ p, cleanup := setupAgent(false)
+ defer cleanup()
+ runSignBenchmark(b, p)
}
func BenchmarkDefaultNoAgent(b *testing.B) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
- runDefaultBenchmark(b, v23.GetPrincipal(ctx))
+ p := testutil.NewPrincipal("agentTest")
+ runDefaultBenchmark(b, p)
}
func BenchmarkDefaultCachedAgent(b *testing.B) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
- runDefaultBenchmark(b, setupAgent(ctx, true))
+ p, cleanup := setupAgent(true)
+ defer cleanup()
+ runDefaultBenchmark(b, p)
}
func BenchmarkDefaultUncachedAgent(b *testing.B) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
- runDefaultBenchmark(b, setupAgent(ctx, false))
+ p, cleanup := setupAgent(false)
+ defer cleanup()
+ runDefaultBenchmark(b, p)
}
func BenchmarkRecognizedNegativeNoAgent(b *testing.B) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
- runRecognizedNegativeBenchmark(b, v23.GetPrincipal(ctx))
+ p := testutil.NewPrincipal("agentTest")
+ runRecognizedNegativeBenchmark(b, p)
}
func BenchmarkRecognizedNegativeCachedAgent(b *testing.B) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
- runRecognizedNegativeBenchmark(b, setupAgent(ctx, true))
+ p, cleanup := setupAgent(true)
+ defer cleanup()
+ runRecognizedNegativeBenchmark(b, p)
}
func BenchmarkRecognizedNegativeUncachedAgent(b *testing.B) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
- runRecognizedNegativeBenchmark(b, setupAgent(ctx, false))
+ p, cleanup := setupAgent(false)
+ defer cleanup()
+ runRecognizedNegativeBenchmark(b, p)
}
func BenchmarkRecognizedNoAgent(b *testing.B) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
- runRecognizedBenchmark(b, v23.GetPrincipal(ctx))
+ p := testutil.NewPrincipal("agentTest")
+ runRecognizedBenchmark(b, p)
}
func BenchmarkRecognizedCachedAgent(b *testing.B) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
- runRecognizedBenchmark(b, setupAgent(ctx, true))
+ p, cleanup := setupAgent(true)
+ defer cleanup()
+ runRecognizedBenchmark(b, p)
}
func BenchmarkRecognizedUncachedAgent(b *testing.B) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
- runRecognizedBenchmark(b, setupAgent(ctx, false))
+ p, cleanup := setupAgent(false)
+ defer cleanup()
+ runRecognizedBenchmark(b, p)
}
diff --git a/services/agent/internal/lockfile/lockfile.go b/services/agent/internal/lockfile/lockfile.go
new file mode 100644
index 0000000..0011510
--- /dev/null
+++ b/services/agent/internal/lockfile/lockfile.go
@@ -0,0 +1,106 @@
+// 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 lockfile
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "syscall"
+
+ "v.io/x/lib/vlog"
+)
+
+const lockfileName = "agent.lock"
+const tempLockfileName = "agent.templock"
+const agentSocketName = "agent.sock" // Keep in sync with agentd/main.go
+
+func CreateLockfile(dir string) error {
+ f, err := ioutil.TempFile(dir, tempLockfileName)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ defer os.Remove(f.Name())
+ cmd := makePsCommand(os.Getpid())
+ cmd.Stdout = f
+ cmd.Stderr = nil
+ if err = cmd.Run(); err != nil {
+ return err
+ }
+ lockfile := filepath.Join(dir, lockfileName)
+ err = os.Link(f.Name(), lockfile)
+ if err == nil {
+ return nil
+ }
+
+ // Check for a stale lock
+ lockProcessInfo, err := ioutil.ReadFile(lockfile)
+ if err != nil {
+ return err
+ }
+ if err, running := StillRunning(lockProcessInfo); running {
+ return fmt.Errorf("agentd is already running:\n%s", lockProcessInfo)
+ } else if err != nil {
+ return err
+ }
+
+ // Delete the socket if the old agent left one behind.
+ if err = os.RemoveAll(filepath.Join(dir, agentSocketName)); err != nil {
+ return err
+ }
+
+ // Note(ribrdb): There's a race here between checking the file contents
+ // and deleting the file, but I don't think it will be an issue in normal
+ // usage.
+ return os.Rename(f.Name(), lockfile)
+}
+
+var pidRegex = regexp.MustCompile("\n\\s*(\\d+)")
+
+func StillRunning(expected []byte) (error, bool) {
+ match := pidRegex.FindSubmatch(expected)
+ if match == nil {
+ // Corrupt lockfile. Just delete it.
+ return nil, false
+ }
+ pid, err := strconv.Atoi(string(match[1]))
+ if err != nil {
+ return nil, false
+ }
+ // Go's os turns the standard errors into indecipherable ones,
+ // so use syscall directly.
+ if err := syscall.Kill(pid, syscall.Signal(0)); err != nil {
+ if errno, ok := err.(syscall.Errno); ok && errno == syscall.ESRCH {
+ return nil, false
+ }
+ }
+ cmd := makePsCommand(pid)
+ out, err := cmd.Output()
+ if err != nil {
+ return err, false
+ }
+ return nil, bytes.Equal(expected, out)
+}
+
+func RemoveLockfile(dir string) {
+ path := filepath.Join(dir, lockfileName)
+ if err := os.Remove(path); err != nil {
+ vlog.Infof("Unable to remove lockfile %q: %v", path, err)
+ }
+ path = filepath.Join(dir, agentSocketName)
+ if err := os.RemoveAll(path); err != nil {
+ vlog.Infof("Unable to remove socket %q: %v", path, err)
+ }
+}
+
+func makePsCommand(pid int) *exec.Cmd {
+ return exec.Command("ps", "-o", "pid,ppid,lstart,user,comm", "-p", strconv.Itoa(pid))
+}
diff --git a/services/agent/internal/lockfile/lockfile_test.go b/services/agent/internal/lockfile/lockfile_test.go
new file mode 100644
index 0000000..1540056
--- /dev/null
+++ b/services/agent/internal/lockfile/lockfile_test.go
@@ -0,0 +1,118 @@
+// 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 lockfile
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "v.io/x/ref/test/modules"
+
+ _ "v.io/x/ref/runtime/factories/generic"
+)
+
+//go:generate v23 test generate
+
+var createLockfile = modules.Register(func(env *modules.Env, args ...string) error {
+ dir := args[0]
+ err := CreateLockfile(dir)
+ if err == nil {
+ fmt.Println("Grabbed lock")
+ } else {
+ fmt.Println("Lock failed")
+ }
+ return err
+}, "createLockfile")
+
+func TestLockFile(t *testing.T) {
+ dir, err := ioutil.TempDir("", "lf")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+ if err = CreateLockfile(dir); err != nil {
+ t.Fatal(err)
+ }
+ path := filepath.Join(dir, lockfileName)
+ bytes, err := ioutil.ReadFile(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err, running := StillRunning(bytes)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !running {
+ t.Fatal("expected StillRunning() = true")
+ }
+
+ if err = CreateLockfile(dir); err == nil {
+ t.Fatal("Creating 2nd lockfile should fail")
+ }
+
+ RemoveLockfile(dir)
+ _, err = os.Lstat(path)
+ if !os.IsNotExist(err) {
+ t.Fatalf("%s: expected NotExist, got %v", path, err)
+ }
+}
+
+func TestOtherProcess(t *testing.T) {
+ dir, err := ioutil.TempDir("", "lf")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+
+ sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Start a new child which creates a lockfile and exits.
+ h, err := sh.Start(nil, createLockfile, dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ h.Expect("Grabbed lock")
+ h.Shutdown(os.Stdout, os.Stderr)
+ if h.Failed() {
+ t.Fatal(h.Error())
+ }
+
+ // Verify it created a lockfile.
+ path := filepath.Join(dir, lockfileName)
+ bytes, err := ioutil.ReadFile(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // And that we know the lockfile is invalid.
+ err, running := StillRunning(bytes)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if running {
+ t.Fatal("child process is dead")
+ }
+
+ // Now create a lockfile for the process.
+ if err = CreateLockfile(dir); err != nil {
+ t.Fatal(err)
+ }
+
+ // Now the child should fail to create one.
+ h, err = sh.Start(nil, createLockfile, dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ h.Expect("Lock failed")
+ h.Shutdown(os.Stderr, os.Stderr)
+ if h.Failed() {
+ t.Fatal(h.Error())
+ }
+}
diff --git a/services/agent/internal/lockfile/v23_internal_test.go b/services/agent/internal/lockfile/v23_internal_test.go
new file mode 100644
index 0000000..2ffce56
--- /dev/null
+++ b/services/agent/internal/lockfile/v23_internal_test.go
@@ -0,0 +1,22 @@
+// 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 lockfile
+
+import (
+ "os"
+ "testing"
+
+ "v.io/x/ref/test"
+ "v.io/x/ref/test/modules"
+)
+
+func TestMain(m *testing.M) {
+ test.Init()
+ modules.DispatchAndExitIfChild()
+ os.Exit(m.Run())
+}
diff --git a/services/agent/internal/server/server.go b/services/agent/internal/server/server.go
index 22011b1..ba9c579 100644
--- a/services/agent/internal/server/server.go
+++ b/services/agent/internal/server/server.go
@@ -12,26 +12,17 @@
"crypto/x509"
"encoding/base64"
"fmt"
- "io"
- "net"
"os"
"path/filepath"
- "strconv"
+ "sync"
- "v.io/v23"
- "v.io/v23/context"
- "v.io/v23/options"
- "v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/verror"
vsecurity "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/internal/unixfd"
+ "v.io/x/ref/services/agent/internal/ipc"
)
-const PrincipalHandleByteSize = sha512.Size
-
const pkgPath = "v.io/x/ref/services/agent/internal/server"
// Errors
@@ -42,61 +33,38 @@
verror.NoRetry, "{1:}{2:} Not running in multi-key mode")
)
-type keyHandle [PrincipalHandleByteSize]byte
-
type agentd struct {
- id int
- w *watchers
+ ipc *ipc.IPC
principal security.Principal
- ctx *context.T
+ mu sync.RWMutex
}
type keyData struct {
- w *watchers
- p security.Principal
+ p security.Principal
+ agent *ipc.IPC
}
type keymgr struct {
path string
passphrase []byte
- ctx *context.T
+ cache map[[agent.PrincipalHandleByteSize]byte]keyData
+ mu sync.Mutex
}
-// RunAnonymousAgent starts the agent server listening on an
-// anonymous unix domain socket. It will respond to requests
-// using 'principal'.
-//
-// The returned 'client' and 'endpoint' are typically passed via
-// cmd.ExtraFiles and envvar.AgentEndpoint to a child process.
-//
-// When passing 'endpoint' to a child, set 'remoteFd' to the fd number
-// in the child process. If 'endpoint' will be used in this process
-// (e.g. in the agent unit tests), set 'remoteFd' to -1.
-func RunAnonymousAgent(ctx *context.T, principal security.Principal, remoteFd int) (client *os.File, endpoint string, err error) {
- local, remote, err := unixfd.Socketpair()
- if err != nil {
- return nil, "", err
- }
- if err = startAgent(ctx, local, newWatchers(), principal); err != nil {
- remote.Close()
- return nil, "", err
- }
- if remoteFd == -1 {
- remoteFd = int(remote.Fd())
- }
- return remote, agentlib.AgentEndpoint(remoteFd), nil
+// ServeAgent registers the agent server with 'ipc'.
+// It will respond to requests using 'principal'.
+// Must be called before ipc.Listen or ipc.Connect.
+func ServeAgent(i *ipc.IPC, principal security.Principal) (err error) {
+ server := &agentd{ipc: i, principal: principal}
+ return i.Serve(server)
}
-// RunKeyManager starts the key manager server listening on an anonymous unix
-// domain socket. It will persist principals in 'path' using 'passphrase'.
-// The returned 'client' is typically passed via cmd.ExtraFiles to a child
-// process.
-func RunKeyManager(ctx *context.T, path string, passphrase []byte) (client *os.File, err error) {
+func newKeyManager(path string, passphrase []byte) (*keymgr, error) {
if path == "" {
return nil, verror.New(errStoragePathRequired, nil)
}
- mgr := &keymgr{path: path, passphrase: passphrase, ctx: ctx}
+ mgr := &keymgr{path: path, passphrase: passphrase, cache: make(map[[agent.PrincipalHandleByteSize]byte]keyData)}
if err := os.MkdirAll(filepath.Join(mgr.path, "keys"), 0700); err != nil {
return nil, err
@@ -104,83 +72,50 @@
if err := os.MkdirAll(filepath.Join(mgr.path, "creds"), 0700); err != nil {
return nil, err
}
+ return mgr, nil
+}
- local, client, err := unixfd.Socketpair()
+type localKeymgr struct {
+ *keymgr
+}
+
+func NewLocalKeyManager(path string, passphrase []byte) (agent.KeyManager, error) {
+ m, err := newKeyManager(path, passphrase)
+ return localKeymgr{m}, err
+}
+
+func (l localKeymgr) Close() error {
+ defer l.mu.Unlock()
+ l.mu.Lock()
+ for _, data := range l.cache {
+ if data.agent != nil {
+ data.agent.Close()
+ }
+ }
+ return nil
+}
+
+// ServeKeyManager registers key manager server with 'ipc'.
+// It will persist principals in 'path' using 'passphrase'.
+// Must be called before ipc.Listen or ipc.Connect.
+func ServeKeyManager(i *ipc.IPC, path string, passphrase []byte) error {
+ mgr, err := newKeyManager(path, passphrase)
if err != nil {
- return nil, err
+ return err
}
-
- go mgr.readConns(ctx, local)
-
- return client, nil
+ return i.Serve(mgr)
}
-func (a *keymgr) readConns(ctx *context.T, conn *net.UnixConn) {
- cache := make(map[keyHandle]keyData)
- donech := a.ctx.Done()
- if donech != nil {
- go func() {
- // Shut down our read loop if the context is cancelled
- <-donech
- conn.Close()
- }()
- }
- defer conn.Close()
- var buf keyHandle
- for {
- addr, n, ack, err := unixfd.ReadConnection(conn, buf[:])
- if err == io.EOF {
- return
- } else if err != nil {
- // We ignore read errors, unless the context is cancelled.
- select {
- case <-donech:
- return
- default:
- ctx.Infof("Error accepting connection: %v", err)
- continue
- }
- }
- ack()
- var data keyData
- if n == len(buf) {
- if cached, ok := cache[buf]; ok {
- data = cached
- } else if data, err = a.readKey(buf); err != nil {
- ctx.Error(err)
- continue
- } else {
- cache[buf] = data
- }
- } else if n == 1 {
- if buf, data, err = a.newKey(buf[0] == 1); err != nil {
- ctx.Infof("Error creating key: %v", err)
- unixfd.CloseUnixAddr(addr)
- continue
- }
- cache[buf] = data
- if _, err = conn.Write(buf[:]); err != nil {
- ctx.Infof("Error sending key handle: %v", err)
- unixfd.CloseUnixAddr(addr)
- continue
- }
- } else {
- ctx.Infof("invalid key: %d bytes, expected %d or 1", n, len(buf))
- unixfd.CloseUnixAddr(addr)
- continue
- }
- conn, err := dial(addr)
- if err != nil {
- ctx.Info(err)
- continue
- }
- if err := startAgent(a.ctx, conn, data.w, data.p); err != nil {
- ctx.Infof("error starting agent: %v", err)
+func (a *keymgr) readKey(handle [agent.PrincipalHandleByteSize]byte) (keyData, error) {
+ {
+ a.mu.Lock()
+ cached, ok := a.cache[handle]
+ a.mu.Unlock()
+ if ok {
+ return cached, nil
}
}
-}
-func (a *keymgr) readKey(handle keyHandle) (keyData, error) {
var nodata keyData
filename := base64.URLEncoding.EncodeToString(handle[:])
in, err := os.Open(filepath.Join(a.path, "keys", filename))
@@ -200,82 +135,18 @@
if err != nil {
return nodata, fmt.Errorf("unable to load principal: %v", err)
}
- return keyData{newWatchers(), principal}, nil
+ data := keyData{p: principal}
+ a.mu.Lock()
+ if cachedData, ok := a.cache[handle]; ok {
+ data = cachedData
+ } else {
+ a.cache[handle] = data
+ }
+ a.mu.Unlock()
+ return data, nil
}
-func dial(addr net.Addr) (*net.UnixConn, error) {
- fd, err := strconv.ParseInt(addr.String(), 10, 32)
- if err != nil {
- return nil, fmt.Errorf("invalid address: %v", addr)
- }
- file := os.NewFile(uintptr(fd), "client")
- defer file.Close()
- conn, err := net.FileConn(file)
- if err != nil {
- return nil, fmt.Errorf("unable to create conn: %v", err)
- }
- return conn.(*net.UnixConn), nil
-}
-
-func startAgent(ctx *context.T, conn *net.UnixConn, w *watchers, principal security.Principal) error {
- donech := ctx.Done()
- if donech != nil {
- go func() {
- // Interrupt the read loop if the context is cancelled.
- <-donech
- conn.Close()
- }()
- }
- go func() {
- buf := make([]byte, 1)
- for {
- clientAddr, _, ack, err := unixfd.ReadConnection(conn, buf)
- opErr, isNetOptErr := err.(*net.OpError)
- if err == io.EOF || (isNetOptErr && opErr.Err == io.EOF) {
- conn.Close()
- return
- } else if err != nil {
- // We ignore read errors, unless the context is cancelled.
- select {
- case <-donech:
- return
- default:
- ctx.Infof("Error accepting connection: %#v", err)
- continue
- }
- }
- if clientAddr != nil {
- // SecurityNone is safe since we're using anonymous unix sockets.
- // Only our child process can possibly communicate on the socket.
- //
- // Also, SecurityNone implies that s (rpc.Server) created below does not
- // authenticate to clients, so runtime.Principal is irrelevant for the agent.
- // TODO(ribrdb): Shutdown these servers when the connection is closed.
- s, err := v23.NewServer(ctx, options.SecurityNone)
- if err != nil {
- ctx.Infof("Error creating server: %v", err)
- ack()
- continue
- }
- a := []struct{ Protocol, Address string }{
- {clientAddr.Network(), clientAddr.String()},
- }
- spec := rpc.ListenSpec{Addrs: rpc.ListenAddrs(a)}
- if _, err = s.Listen(spec); err == nil {
- server := agent.AgentServer(&agentd{w.newID(), w, principal, ctx})
- err = s.Serve("", server, nil)
- }
- ack()
- }
- if err != nil {
- ctx.Infof("Error accepting connection: %v", err)
- }
- }
- }()
- return nil
-}
-
-func (a agentd) Bless(_ *context.T, _ rpc.ServerCall, key []byte, with security.Blessings, extension string, caveat security.Caveat, additionalCaveats []security.Caveat) (security.Blessings, error) {
+func (a *agentd) Bless(key []byte, with security.Blessings, extension string, caveat security.Caveat, additionalCaveats []security.Caveat) (security.Blessings, error) {
pkey, err := security.UnmarshalPublicKey(key)
if err != nil {
return security.Blessings{}, err
@@ -283,61 +154,65 @@
return a.principal.Bless(pkey, with, extension, caveat, additionalCaveats...)
}
-func (a agentd) BlessSelf(_ *context.T, _ rpc.ServerCall, name string, caveats []security.Caveat) (security.Blessings, error) {
+func (a *agentd) BlessSelf(name string, caveats []security.Caveat) (security.Blessings, error) {
return a.principal.BlessSelf(name, caveats...)
}
-func (a agentd) Sign(_ *context.T, _ rpc.ServerCall, message []byte) (security.Signature, error) {
+func (a *agentd) Sign(message []byte) (security.Signature, error) {
return a.principal.Sign(message)
}
-func (a agentd) MintDischarge(_ *context.T, _ rpc.ServerCall, forCaveat, caveatOnDischarge security.Caveat, additionalCaveatsOnDischarge []security.Caveat) (security.Discharge, error) {
+func (a *agentd) MintDischarge(forCaveat, caveatOnDischarge security.Caveat, additionalCaveatsOnDischarge []security.Caveat) (security.Discharge, error) {
return a.principal.MintDischarge(forCaveat, caveatOnDischarge, additionalCaveatsOnDischarge...)
}
-func (a *keymgr) newKey(in_memory bool) (keyHandle, keyData, error) {
- var handle keyHandle
- var nodata keyData
+func (a *keymgr) NewPrincipal(in_memory bool) (handle [agent.PrincipalHandleByteSize]byte, err error) {
if a.path == "" {
- return handle, nodata, verror.New(errNotMultiKeyMode, nil)
+ return handle, verror.New(errNotMultiKeyMode, nil)
}
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
- return handle, nodata, err
+ return handle, err
}
if handle, err = keyid(key); err != nil {
- return handle, nodata, err
+ return handle, err
}
signer := security.NewInMemoryECDSASigner(key)
var p security.Principal
if in_memory {
if p, err = vsecurity.NewPrincipalFromSigner(signer, nil); err != nil {
- return handle, nodata, err
+ return handle, err
}
} else {
filename := base64.URLEncoding.EncodeToString(handle[:])
out, err := os.OpenFile(filepath.Join(a.path, "keys", filename), os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
- return handle, nodata, err
+ return handle, err
}
defer out.Close()
err = vsecurity.SavePEMKey(out, key, a.passphrase)
if err != nil {
- return handle, nodata, err
+ return handle, err
}
state, err := vsecurity.NewPrincipalStateSerializer(filepath.Join(a.path, "creds", filename))
if err != nil {
- return handle, nodata, err
+ return handle, err
}
p, err = vsecurity.NewPrincipalFromSigner(signer, state)
if err != nil {
- return handle, nodata, err
+ return handle, err
}
}
- return handle, keyData{newWatchers(), p}, nil
+ data := keyData{p: p}
+ a.mu.Lock()
+ if _, ok := a.cache[handle]; !ok {
+ a.cache[handle] = data
+ }
+ a.mu.Unlock()
+ return handle, nil
}
-func keyid(key *ecdsa.PrivateKey) (handle keyHandle, err error) {
+func keyid(key *ecdsa.PrivateKey) (handle [agent.PrincipalHandleByteSize]byte, err error) {
slice, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
if err != nil {
return
@@ -345,108 +220,114 @@
return sha512.Sum512(slice), nil
}
-func (a agentd) PublicKey(_ *context.T, _ rpc.ServerCall) ([]byte, error) {
+func (a *agentd) unlock() {
+ a.mu.Unlock()
+ for _, conn := range a.ipc.Connections() {
+ go conn.Call("FlushAllCaches", nil)
+ }
+}
+
+func (a *agentd) PublicKey() ([]byte, error) {
return a.principal.PublicKey().MarshalBinary()
}
-func (a agentd) BlessingsByName(_ *context.T, _ rpc.ServerCall, name security.BlessingPattern) ([]security.Blessings, error) {
- a.w.rlock()
- defer a.w.runlock()
+func (a *agentd) BlessingsByName(name security.BlessingPattern) ([]security.Blessings, error) {
+ defer a.mu.RUnlock()
+ a.mu.RLock()
return a.principal.BlessingsByName(name), nil
}
-func (a agentd) BlessingsInfo(_ *context.T, _ rpc.ServerCall, blessings security.Blessings) (map[string][]security.Caveat, error) {
- a.w.rlock()
- defer a.w.runlock()
+func (a *agentd) BlessingsInfo(blessings security.Blessings) (map[string][]security.Caveat, error) {
+ a.mu.RLock()
return a.principal.BlessingsInfo(blessings), nil
}
-func (a agentd) AddToRoots(_ *context.T, _ rpc.ServerCall, blessings security.Blessings) error {
- a.w.lock()
- defer a.w.unlock(a.id)
+func (a *agentd) AddToRoots(blessings security.Blessings) error {
+ defer a.unlock()
+ a.mu.Lock()
return a.principal.AddToRoots(blessings)
}
-func (a agentd) BlessingStoreSet(_ *context.T, _ rpc.ServerCall, blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error) {
- a.w.lock()
- defer a.w.unlock(a.id)
+func (a *agentd) BlessingStoreSet(blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error) {
+ defer a.unlock()
+ a.mu.Lock()
return a.principal.BlessingStore().Set(blessings, forPeers)
}
-func (a agentd) BlessingStoreForPeer(_ *context.T, _ rpc.ServerCall, peerBlessings []string) (security.Blessings, error) {
- a.w.rlock()
- defer a.w.runlock()
+func (a *agentd) BlessingStoreForPeer(peerBlessings []string) (security.Blessings, error) {
+ defer a.mu.RUnlock()
+ a.mu.RLock()
return a.principal.BlessingStore().ForPeer(peerBlessings...), nil
}
-func (a agentd) BlessingStoreSetDefault(_ *context.T, _ rpc.ServerCall, blessings security.Blessings) error {
- a.w.lock()
- defer a.w.unlock(a.id)
+func (a *agentd) BlessingStoreSetDefault(blessings security.Blessings) error {
+ defer a.unlock()
+ a.mu.Lock()
return a.principal.BlessingStore().SetDefault(blessings)
}
-func (a agentd) BlessingStorePeerBlessings(_ *context.T, _ rpc.ServerCall) (map[security.BlessingPattern]security.Blessings, error) {
- a.w.rlock()
- defer a.w.runlock()
+func (a *agentd) BlessingStorePeerBlessings() (map[security.BlessingPattern]security.Blessings, error) {
+ defer a.mu.RUnlock()
+ a.mu.RLock()
return a.principal.BlessingStore().PeerBlessings(), nil
}
-func (a agentd) BlessingStoreDebugString(_ *context.T, _ rpc.ServerCall) (string, error) {
- a.w.rlock()
- defer a.w.runlock()
+func (a *agentd) BlessingStoreDebugString() (string, error) {
+ defer a.mu.RUnlock()
+ a.mu.RLock()
return a.principal.BlessingStore().DebugString(), nil
}
-func (a agentd) BlessingStoreDefault(_ *context.T, _ rpc.ServerCall) (security.Blessings, error) {
- a.w.rlock()
- defer a.w.runlock()
+func (a *agentd) BlessingStoreDefault() (security.Blessings, error) {
+ defer a.mu.RUnlock()
+ a.mu.RLock()
return a.principal.BlessingStore().Default(), nil
}
-func (a agentd) BlessingStoreCacheDischarge(_ *context.T, _ rpc.ServerCall, discharge security.Discharge, caveat security.Caveat, impetus security.DischargeImpetus) error {
- a.w.lock()
+func (a *agentd) BlessingStoreCacheDischarge(discharge security.Discharge, caveat security.Caveat, impetus security.DischargeImpetus) error {
+ defer a.mu.Unlock()
+ a.mu.Lock()
a.principal.BlessingStore().CacheDischarge(discharge, caveat, impetus)
- a.w.unlock(a.id)
return nil
}
-func (a agentd) BlessingStoreClearDischarges(_ *context.T, _ rpc.ServerCall, discharges []security.Discharge) error {
- a.w.lock()
+func (a *agentd) BlessingStoreClearDischarges(discharges []security.Discharge) error {
+ defer a.mu.Unlock()
+ a.mu.Lock()
a.principal.BlessingStore().ClearDischarges(discharges...)
- a.w.unlock(a.id)
return nil
}
-func (a agentd) BlessingStoreDischarge(_ *context.T, _ rpc.ServerCall, caveat security.Caveat, impetus security.DischargeImpetus) (security.Discharge, error) {
- a.w.lock()
- defer a.w.unlock(a.id)
+func (a *agentd) BlessingStoreDischarge(caveat security.Caveat, impetus security.DischargeImpetus) (security.Discharge, error) {
+ defer a.mu.Unlock()
+ a.mu.Lock()
return a.principal.BlessingStore().Discharge(caveat, impetus), nil
}
-func (a agentd) BlessingRootsAdd(_ *context.T, _ rpc.ServerCall, root []byte, pattern security.BlessingPattern) error {
+func (a *agentd) BlessingRootsAdd(root []byte, pattern security.BlessingPattern) error {
pkey, err := security.UnmarshalPublicKey(root)
if err != nil {
return err
}
- a.w.lock()
- defer a.w.unlock(a.id)
+ defer a.unlock()
+ a.mu.Lock()
return a.principal.Roots().Add(pkey, pattern)
}
-func (a agentd) BlessingRootsRecognized(_ *context.T, _ rpc.ServerCall, root []byte, blessing string) error {
+func (a *agentd) BlessingRootsRecognized(root []byte, blessing string) error {
pkey, err := security.UnmarshalPublicKey(root)
if err != nil {
return err
}
- a.w.rlock()
- defer a.w.runlock()
+ defer a.mu.RUnlock()
+ a.mu.RLock()
return a.principal.Roots().Recognized(pkey, blessing)
}
-func (a agentd) BlessingRootsDump(_ *context.T, _ rpc.ServerCall) (map[security.BlessingPattern][][]byte, error) {
+func (a *agentd) BlessingRootsDump() (map[security.BlessingPattern][][]byte, error) {
ret := make(map[security.BlessingPattern][][]byte)
- a.w.rlock()
- defer a.w.runlock()
+ defer a.mu.RUnlock()
+ a.mu.RLock()
for p, keys := range a.principal.Roots().Dump() {
for _, key := range keys {
marshaledKey, err := key.MarshalBinary()
@@ -459,28 +340,73 @@
return ret, nil
}
-func (a agentd) BlessingRootsDebugString(_ *context.T, _ rpc.ServerCall) (string, error) {
- a.w.rlock()
- defer a.w.runlock()
+func (a *agentd) BlessingRootsDebugString() (string, error) {
+ defer a.mu.RUnlock()
+ a.mu.RLock()
return a.principal.Roots().DebugString(), nil
}
-func (a agentd) NotifyWhenChanged(ctx *context.T, call agent.AgentNotifyWhenChangedServerCall) error {
- ch := a.w.register(a.id)
- defer a.w.unregister(a.id, ch)
- for {
- select {
- case <-a.ctx.Done():
- return nil
- case <-ctx.Done():
- return nil
- case _, ok := <-ch:
- if !ok {
- return nil
- }
- if err := call.SendStream().Send(true); err != nil {
- return err
- }
- }
+func (m *keymgr) ServePrincipal(handle [agent.PrincipalHandleByteSize]byte, path string) error {
+ if _, err := m.readKey(handle); err != nil {
+ return err
}
+ defer m.mu.Unlock()
+ m.mu.Lock()
+ data, ok := m.cache[handle]
+ if !ok {
+ return fmt.Errorf("key deleted")
+ }
+ if data.agent != nil {
+ return verror.NewErrExist(nil)
+ }
+ ipc := ipc.NewIPC()
+ if err := ServeAgent(ipc, data.p); err != nil {
+ return err
+ }
+ if err := ipc.Listen(path); err != nil {
+ return err
+ }
+ data.agent = ipc
+ m.cache[handle] = data
+ return nil
+}
+
+func (m *keymgr) StopServing(handle [agent.PrincipalHandleByteSize]byte) error {
+ if _, err := m.readKey(handle); err != nil {
+ return err
+ }
+ defer m.mu.Unlock()
+ m.mu.Lock()
+ data, ok := m.cache[handle]
+ if !ok {
+ return fmt.Errorf("key deleted")
+ }
+ if data.agent == nil {
+ return verror.NewErrNoExist(nil)
+ }
+ data.agent.Close()
+ data.agent = nil
+ m.cache[handle] = data
+ return nil
+}
+
+func (m *keymgr) DeletePrincipal(handle [agent.PrincipalHandleByteSize]byte) error {
+ defer m.mu.Unlock()
+ m.mu.Lock()
+ data, cached := m.cache[handle]
+ if cached {
+ if data.agent != nil {
+ data.agent.Close()
+ }
+ delete(m.cache, handle)
+ }
+ filename := base64.URLEncoding.EncodeToString(handle[:])
+ keyErr := os.Remove(filepath.Join(m.path, "keys", filename))
+ credErr := os.RemoveAll(filepath.Join(m.path, "creds", filename))
+ if os.IsNotExist(keyErr) && !cached {
+ return verror.NewErrNoExist(nil)
+ } else if keyErr != nil {
+ return keyErr
+ }
+ return credErr
}
diff --git a/services/agent/internal/test_principal/main.go b/services/agent/internal/test_principal/main.go
index 7887d67..40c4f5c 100644
--- a/services/agent/internal/test_principal/main.go
+++ b/services/agent/internal/test_principal/main.go
@@ -58,8 +58,8 @@
if got := env.Vars[ref.EnvCredentials]; len(got) != 0 {
errorf("%v environment variable is unexpectedly set", ref.EnvCredentials)
}
- if got := env.Vars[ref.EnvAgentEndpoint]; len(got) == 0 {
- errorf("%v environment variable is not set", ref.EnvAgentEndpoint)
+ if got := env.Vars[ref.EnvAgentPath]; len(got) == 0 {
+ errorf("%v environment variable is not set", ref.EnvAgentPath)
}
// A pristine agent has a single blessing "agent_principal" (from agentd/main.go).
if blessings := p.BlessingsInfo(p.BlessingStore().Default()); len(blessings) != 1 {
diff --git a/services/agent/keymgr/client.go b/services/agent/keymgr/client.go
index ae837dd..7515f58 100644
--- a/services/agent/keymgr/client.go
+++ b/services/agent/keymgr/client.go
@@ -32,7 +32,7 @@
const defaultManagerSocket = 4
-type KeyManager struct {
+type keyManager struct {
conn *ipc.IPCConn
}
@@ -50,23 +50,15 @@
func NewKeyManager(path string) (agent.KeyManager, error) {
i := ipc.NewIPC()
conn, err := i.Connect(path)
- var m *KeyManager
+ var m *keyManager
if err == nil {
- m = &KeyManager{conn}
+ m = &keyManager{conn}
}
return m, err
}
-func NewLocalAgent(ctx *context.T, path string, passphrase []byte) (*Agent, error) {
- file, err := server.RunKeyManager(ctx, path, passphrase)
- if err != nil {
- return nil, err
- }
- conn, err := net.FileConn(file)
- if err != nil {
- return nil, err
- }
- return &Agent{conn: conn.(*net.UnixConn)}, nil
+func NewLocalAgent(path string, passphrase []byte) (agent.KeyManager, error) {
+ return server.NewLocalKeyManager(path, passphrase)
}
func newAgent(fd int) (a *Agent, err error) {
@@ -111,7 +103,7 @@
// NewPrincipal creates a new principal and returns a handle.
// The handle may be passed to ServePrincipal to start an agent serving the principal.
-func (m *KeyManager) NewPrincipal(inMemory bool) (handle [agent.PrincipalHandleByteSize]byte, err error) {
+func (m *keyManager) NewPrincipal(inMemory bool) (handle [agent.PrincipalHandleByteSize]byte, err error) {
args := []interface{}{inMemory}
err = m.conn.Call("NewPrincipal", args, &handle)
return
@@ -143,7 +135,7 @@
// ServePrincipal creates a socket at socketPath and serves a principal
// previously created with NewPrincipal.
-func (m *KeyManager) ServePrincipal(handle [agent.PrincipalHandleByteSize]byte, socketPath string) error {
+func (m *keyManager) ServePrincipal(handle [agent.PrincipalHandleByteSize]byte, socketPath string) error {
args := []interface{}{handle, socketPath}
return m.conn.Call("ServePrincipal", args)
}
@@ -151,19 +143,19 @@
// StopServing shuts down a server previously started with ServePrincipal.
// The principal is not deleted and the server can be restarted by calling
// ServePrincipal again.
-func (m *KeyManager) StopServing(handle [agent.PrincipalHandleByteSize]byte) error {
+func (m *keyManager) StopServing(handle [agent.PrincipalHandleByteSize]byte) error {
args := []interface{}{handle}
return m.conn.Call("StopServing", args)
}
// DeletePrincipal shuts down a server started by ServePrincipal and additionally
// deletes the principal.
-func (m *KeyManager) DeletePrincipal(handle [agent.PrincipalHandleByteSize]byte) error {
+func (m *keyManager) DeletePrincipal(handle [agent.PrincipalHandleByteSize]byte) error {
args := []interface{}{handle}
return m.conn.Call("DeletePrincipal", args)
}
-func (m *KeyManager) Close() error {
+func (m *keyManager) Close() error {
m.conn.Close()
return nil
}
diff --git a/services/agent/keymgr/keymgr_test.go b/services/agent/keymgr/keymgr_test.go
index dd0a334..7a4bd77 100644
--- a/services/agent/keymgr/keymgr_test.go
+++ b/services/agent/keymgr/keymgr_test.go
@@ -9,45 +9,42 @@
"os"
"path/filepath"
"reflect"
- "syscall"
"testing"
- "v.io/v23"
- "v.io/v23/context"
"v.io/v23/security"
+ "v.io/v23/verror"
+ "v.io/x/ref/services/agent"
"v.io/x/ref/services/agent/agentlib"
+ "v.io/x/ref/services/agent/internal/ipc"
"v.io/x/ref/services/agent/internal/server"
- "v.io/x/ref/test"
_ "v.io/x/ref/runtime/factories/generic"
)
-func createAgent(ctx *context.T, path string) (*Agent, func(), error) {
+func createAgent(path string) (agent.KeyManager, func(), error) {
var defers []func()
cleanup := func() {
for _, f := range defers {
f()
}
}
- sock, err := server.RunKeyManager(ctx, path, nil)
- var agent *Agent
- if sock != nil {
- defers = append(defers, func() { os.RemoveAll(path) })
- defers = append(defers, func() { sock.Close() })
- fd, err := syscall.Dup(int(sock.Fd()))
- if err != nil {
- return nil, cleanup, err
- }
- agent, err = newAgent(fd)
+ i := ipc.NewIPC()
+ if err := server.ServeKeyManager(i, path, nil); err != nil {
+ return nil, cleanup, err
}
- return agent, cleanup, err
+ defers = append(defers, func() { os.RemoveAll(path) })
+ sock := filepath.Join(path, "keymgr.sock")
+ if err := i.Listen(sock); err != nil {
+ return nil, cleanup, err
+ }
+ defers = append(defers, i.Close)
+
+ m, err := NewKeyManager(sock)
+ return m, cleanup, err
}
func TestNoDeviceManager(t *testing.T) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
-
- agent, cleanup, err := createAgent(ctx, "")
+ agent, cleanup, err := createAgent("")
defer cleanup()
if err == nil {
t.Fatal(err)
@@ -57,52 +54,38 @@
}
}
-func createClient(ctx *context.T, deviceAgent *Agent, id []byte) (security.Principal, error) {
- file, err := deviceAgent.NewConnection(id)
+func createClient(deviceAgent agent.KeyManager, id [64]byte) (security.Principal, error) {
+ dir, err := ioutil.TempDir("", "conn")
if err != nil {
return nil, err
}
- defer file.Close()
- return createClient2(ctx, file)
-}
-
-func createClient2(ctx *context.T, conn *os.File) (security.Principal, error) {
- fd, err := syscall.Dup(int(conn.Fd()))
- if err != nil {
+ path := filepath.Join(dir, "sock")
+ if err := deviceAgent.ServePrincipal(id, path); err != nil {
return nil, err
}
-
- ep, err := v23.NewEndpoint(agentlib.AgentEndpoint(fd))
- if err != nil {
- return nil, err
- }
- return agentlib.NewAgentPrincipal(ctx, ep, v23.GetClient(ctx))
+ defer os.RemoveAll(dir)
+ return agentlib.NewAgentPrincipalX(path)
}
func TestSigning(t *testing.T) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
-
path, err := ioutil.TempDir("", "agent")
if err != nil {
t.Fatal(err)
}
- agent, cleanup, err := createAgent(ctx, path)
+ agent, cleanup, err := createAgent(path)
defer cleanup()
if err != nil {
t.Fatal(err)
}
- id1, conn1, err := agent.NewPrincipal(ctx, false)
+ id1, err := agent.NewPrincipal(false)
if err != nil {
t.Fatal(err)
}
- conn1.Close()
- id2, conn2, err := agent.NewPrincipal(ctx, false)
+ id2, err := agent.NewPrincipal(false)
if err != nil {
t.Fatal(err)
}
- conn2.Close()
dir, err := os.Open(filepath.Join(path, "keys"))
if err != nil {
@@ -116,11 +99,16 @@
t.Errorf("Expected 2 files created, found %d", len(files))
}
- a, err := createClient(ctx, agent, id1)
+ a, err := createClient(agent, id1)
if err != nil {
t.Fatal(err)
}
- b, err := createClient(ctx, agent, id2)
+ // Serving again should be an error
+ if _, err := createClient(agent, id1); verror.ErrorID(err) != verror.ErrExist.ID {
+ t.Fatalf("Expected ErrExist, got %v", err)
+ }
+
+ b, err := createClient(agent, id2)
if err != nil {
t.Fatal(err)
}
@@ -147,20 +135,17 @@
}
func TestInMemorySigning(t *testing.T) {
- ctx, shutdown := test.V23Init()
- defer shutdown()
-
path, err := ioutil.TempDir("", "agent")
if err != nil {
t.Fatal(err)
}
- agent, cleanup, err := createAgent(ctx, path)
+ agent, cleanup, err := createAgent(path)
defer cleanup()
if err != nil {
t.Fatal(err)
}
- id, conn, err := agent.NewPrincipal(ctx, true)
+ id, err := agent.NewPrincipal(true)
if err != nil {
t.Fatal(err)
}
@@ -177,7 +162,7 @@
t.Errorf("Expected 0 files created, found %d", len(files))
}
- c, err := createClient2(ctx, conn)
+ c, err := createClient(agent, id)
if err != nil {
t.Fatal(err)
}
@@ -188,16 +173,4 @@
if !sig.Verify(c.PublicKey(), []byte("foobar")) {
t.Errorf("Signature a fails verification")
}
-
- c2, err := createClient(ctx, agent, id)
- if err != nil {
- t.Fatal(err)
- }
- sig, err = c2.Sign([]byte("foobar"))
- if err != nil {
- t.Fatal(err)
- }
- if !sig.Verify(c.PublicKey(), []byte("foobar")) {
- t.Errorf("Signature a fails verification")
- }
}
diff --git a/services/agent/vbecome/vbecome.go b/services/agent/vbecome/vbecome.go
index 06cd922..15abcc0 100644
--- a/services/agent/vbecome/vbecome.go
+++ b/services/agent/vbecome/vbecome.go
@@ -12,6 +12,7 @@
"crypto/elliptic"
"crypto/rand"
"fmt"
+ "io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -25,6 +26,7 @@
"v.io/x/ref"
vsecurity "v.io/x/ref/lib/security"
"v.io/x/ref/lib/v23cmd"
+ "v.io/x/ref/services/agent/internal/ipc"
"v.io/x/ref/services/agent/internal/server"
"v.io/x/ref/services/role"
@@ -108,15 +110,25 @@
}
// Start an agent server.
- sock, endpoint, err := server.RunAnonymousAgent(ctx, principal, childAgentFd)
+ i := ipc.NewIPC()
+ if err := server.ServeAgent(i, principal); err != nil {
+ return err
+ }
+ dir, err := ioutil.TempDir("", "vbecome")
if err != nil {
return err
}
- if err = os.Setenv(ref.EnvAgentEndpoint, endpoint); err != nil {
+ defer os.RemoveAll(dir)
+ path := filepath.Join(dir, "sock")
+ if err := i.Listen(path); err != nil {
+ return err
+ }
+ defer i.Close()
+ if err = os.Setenv(ref.EnvAgentPath, path); err != nil {
ctx.Fatalf("setenv: %v", err)
}
- return doExec(args, sock)
+ return doExec(args)
}
func bless(ctx *context.T, p security.Principal, name string) error {
@@ -148,12 +160,11 @@
return nil
}
-func doExec(args []string, sock *os.File) error {
+func doExec(args []string) 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()
}
diff --git a/test/modules/exec.go b/test/modules/exec.go
index 9088dea..44a6a1a 100644
--- a/test/modules/exec.go
+++ b/test/modules/exec.go
@@ -20,7 +20,6 @@
"v.io/x/ref/internal/logger"
vexec "v.io/x/ref/lib/exec"
"v.io/x/ref/lib/mgmt"
- "v.io/x/ref/services/agent/agentlib"
"v.io/x/ref/test/expect"
)
@@ -114,7 +113,7 @@
return newargs, envvar.MapToSlice(newenv)
}
-func (eh *execHandle) start(sh *Shell, agentfd *os.File, opts *StartOpts, env []string, args []string) (*execHandle, error) {
+func (eh *execHandle) start(sh *Shell, agentPath string, opts *StartOpts, env []string, args []string) (*execHandle, error) {
eh.mu.Lock()
defer eh.mu.Unlock()
eh.sh = sh
@@ -160,11 +159,8 @@
return nil, err
}
config.MergeFrom(serialized)
- if agentfd != nil {
- childfd := len(cmd.ExtraFiles) + vexec.FileOffset
- config.Set(mgmt.SecurityAgentEndpointConfigKey, agentlib.AgentEndpoint(childfd))
- cmd.ExtraFiles = append(cmd.ExtraFiles, agentfd)
- defer agentfd.Close()
+ if agentPath != "" {
+ config.Set(mgmt.SecurityAgentPathConfigKey, agentPath)
}
execOpts = append(execOpts, vexec.ConfigOpt{Config: config})
}
diff --git a/test/modules/shell.go b/test/modules/shell.go
index 0f4106a..4b3400e 100644
--- a/test/modules/shell.go
+++ b/test/modules/shell.go
@@ -138,6 +138,7 @@
"io"
"io/ioutil"
"os"
+ "path/filepath"
"sync"
"syscall"
"time"
@@ -152,6 +153,7 @@
"v.io/x/ref"
"v.io/x/ref/internal/logger"
"v.io/x/ref/lib/exec"
+ "v.io/x/ref/services/agent"
"v.io/x/ref/services/agent/agentlib"
"v.io/x/ref/services/agent/keymgr"
"v.io/x/ref/test/expect"
@@ -184,7 +186,7 @@
tempCredDir string
config exec.Config
principal security.Principal
- agent *keymgr.Agent
+ agent agent.KeyManager
ctx *context.T
logger logging.Logger
sessionVerbosity bool
@@ -230,7 +232,7 @@
if sh.tempCredDir, err = ioutil.TempDir("", "shell_credentials-"); err != nil {
return nil, err
}
- if sh.agent, err = keymgr.NewLocalAgent(ctx, sh.tempCredDir, nil); err != nil {
+ if sh.agent, err = keymgr.NewLocalAgent(sh.tempCredDir, nil); err != nil {
return nil, err
}
sh.principal = p
@@ -253,9 +255,8 @@
// CustomCredentials encapsulates a Principal which can be shared with
// one or more processes run by a Shell.
type CustomCredentials struct {
- p security.Principal
- agent *keymgr.Agent
- id []byte
+ p security.Principal
+ path string
}
// Principal returns the Principal.
@@ -263,11 +264,10 @@
return c.p
}
-// File returns a socket which can be used to connect to the agent
-// managing this principal. Typically you would pass this to a child
-// process.
-func (c *CustomCredentials) File() (*os.File, error) {
- return c.agent.NewConnection(c.id)
+// Path returns the path to the credential's agent.
+// Typically you would pass this to a child process in EnvAgentPath.
+func (c *CustomCredentials) Path() string {
+ return c.path
}
func dup(conn *os.File) (int, error) {
@@ -289,26 +289,23 @@
if sh.ctx == nil {
return nil, nil
}
- id, conn, err := sh.agent.NewPrincipal(sh.ctx, true)
+ id, err := sh.agent.NewPrincipal(true)
if err != nil {
return nil, err
}
- fd, err := dup(conn)
- conn.Close()
+ dir, err := ioutil.TempDir(sh.tempCredDir, "agent")
if err != nil {
return nil, err
}
- ep, err := v23.NewEndpoint(agentlib.AgentEndpoint(fd))
- if err != nil {
- syscall.Close(fd)
+ path := filepath.Join(dir, "sock")
+ if err := sh.agent.ServePrincipal(id, path); err != nil {
return nil, err
}
- p, err := agentlib.NewAgentPrincipal(sh.ctx, ep, v23.GetClient(sh.ctx))
+ p, err := agentlib.NewAgentPrincipalX(path)
if err != nil {
- syscall.Close(fd)
return nil, err
}
- return &CustomCredentials{p, sh.agent, id}, nil
+ return &CustomCredentials{p, path}, nil
}
// NewChildCredentials creates a new principal, served via the security agent
@@ -549,16 +546,13 @@
}
}
- var p *os.File
+ var agentPath string
if opts.Credentials != nil {
- p, err = opts.Credentials.File()
- if err != nil {
- return nil, err
- }
+ agentPath = opts.Credentials.Path()
}
handle := info.factory()
- h, err := handle.start(sh, p, &opts, sh.setupProgramEnv(env), sh.expand(args))
+ h, err := handle.start(sh, agentPath, &opts, sh.setupProgramEnv(env), sh.expand(args))
if err != nil {
return h, err
}
@@ -730,6 +724,7 @@
// by the shell's VeyronCredentials.
delete(m1, ref.EnvCredentials)
delete(m1, ref.EnvAgentEndpoint)
+ delete(m1, ref.EnvAgentPath)
m2 := envvar.MergeMaps(m1, evmap)
return envvar.MapToSlice(m2)
diff --git a/test/v23tests/v23tests.go b/test/v23tests/v23tests.go
index 165749d..75abecb 100644
--- a/test/v23tests/v23tests.go
+++ b/test/v23tests/v23tests.go
@@ -24,7 +24,6 @@
"v.io/v23/security"
"v.io/x/ref"
- "v.io/x/ref/services/agent/agentlib"
"v.io/x/ref/test"
"v.io/x/ref/test/modules"
"v.io/x/ref/test/testutil"
@@ -347,11 +346,9 @@
return
}
- var agentFile *os.File
+ var agentPath string
if creds, err := t.shell.NewChildCredentials("debug"); err == nil {
- if agentFile, err = creds.File(); err != nil {
- t.ctx.Errorf("WARNING: failed to obtain credentials for the debug shell: %v", err)
- }
+ agentPath = creds.Path()
} else {
t.ctx.Errorf("WARNING: failed to obtain credentials for the debug shell: %v", err)
}
@@ -362,8 +359,7 @@
Dir: cwd,
}
// Set up agent for Child.
- attr.Files = append(attr.Files, agentFile)
- attr.Env = append(attr.Env, fmt.Sprintf("%s=%v", ref.EnvAgentEndpoint, agentlib.AgentEndpoint(len(attr.Files)-1)))
+ attr.Env = append(attr.Env, fmt.Sprintf("%s=%v", ref.EnvAgentPath, agentPath))
// Set up environment for Child.
for _, v := range t.shell.Env() {