veyron2/services/mgmt/node,veyron/services/mgmt/node,veyron/tools/mgmt: rename node->device, cl 3 of N (go/vissue/496)
Rename files and packages.
Change-Id: I29199a97c2ca6d6eb721e0d9fe41a71044e52579
diff --git a/services/mgmt/device/impl/dispatcher.go b/services/mgmt/device/impl/dispatcher.go
new file mode 100644
index 0000000..2dc4e30
--- /dev/null
+++ b/services/mgmt/device/impl/dispatcher.go
@@ -0,0 +1,478 @@
+package impl
+
+import (
+ "bytes"
+ "crypto/md5"
+ "encoding/hex"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+ "sync"
+
+ "veyron.io/veyron/veyron/security/agent"
+ "veyron.io/veyron/veyron/security/agent/keymgr"
+ vflag "veyron.io/veyron/veyron/security/flag"
+ "veyron.io/veyron/veyron/security/serialization"
+ idevice "veyron.io/veyron/veyron/services/mgmt/device"
+ "veyron.io/veyron/veyron/services/mgmt/device/config"
+ logsimpl "veyron.io/veyron/veyron/services/mgmt/logreader/impl"
+
+ "veyron.io/veyron/veyron2/ipc"
+ "veyron.io/veyron/veyron2/naming"
+ "veyron.io/veyron/veyron2/security"
+ "veyron.io/veyron/veyron2/services/mgmt/device"
+ "veyron.io/veyron/veyron2/services/mgmt/pprof"
+ "veyron.io/veyron/veyron2/services/mgmt/stats"
+ "veyron.io/veyron/veyron2/services/security/access"
+ "veyron.io/veyron/veyron2/verror"
+ "veyron.io/veyron/veyron2/verror2"
+ "veyron.io/veyron/veyron2/vlog"
+)
+
+// internalState wraps state shared between different device manager
+// invocations.
+type internalState struct {
+ callback *callbackState
+ updating *updatingState
+ securityAgent *securityAgentState
+}
+
+// aclLocks provides a mutex lock for each acl file path.
+type aclLocks map[string]*sync.Mutex
+
+// dispatcher holds the state of the device manager dispatcher.
+type dispatcher struct {
+ // acl/auth hold the acl and authorizer used to authorize access to the
+ // device manager methods.
+ acl access.TaggedACLMap
+ auth security.Authorizer
+ // etag holds the version string for the ACL. We use this for optimistic
+ // concurrency control when clients update the ACLs for the device
+ // manager.
+ etag string
+ // internal holds the state that persists across RPC method invocations.
+ internal *internalState
+ // config holds the device manager's (immutable) configuration state.
+ config *config.State
+ // dispatcherMutex is a lock for coordinating concurrent access to some
+ // dispatcher methods.
+ mu sync.RWMutex
+ // TODO(rjkroege): Consider moving this inside internal.
+ uat BlessingSystemAssociationStore
+ // TODO(rjkroege): Eliminate need for locks.
+ locks aclLocks
+}
+
+var _ ipc.Dispatcher = (*dispatcher)(nil)
+
+const (
+ appsSuffix = "apps"
+ deviceSuffix = "nm"
+ configSuffix = "cfg"
+
+ pkgPath = "veyron.io/veyron/veyron/services/mgmt/device/impl"
+)
+
+var (
+ ErrInvalidSuffix = verror2.Register(pkgPath+".InvalidSuffix", verror2.NoRetry, "")
+ ErrOperationFailed = verror2.Register(pkgPath+".OperationFailed", verror2.NoRetry, "")
+ ErrOperationInProgress = verror2.Register(pkgPath+".OperationInProgress", verror2.NoRetry, "")
+ ErrAppTitleMismatch = verror2.Register(pkgPath+".AppTitleMismatch", verror2.NoRetry, "")
+ ErrUpdateNoOp = verror2.Register(pkgPath+".UpdateNoOp", verror2.NoRetry, "")
+ ErrObjectNoExist = verror2.Register(pkgPath+".ObjectNoExist", verror2.NoRetry, "")
+ ErrInvalidOperation = verror2.Register(pkgPath+".InvalidOperation", verror2.NoRetry, "")
+ ErrInvalidBlessing = verror2.Register(pkgPath+".InvalidBlessing", verror2.NoRetry, "")
+)
+
+// NewDispatcher is the device manager dispatcher factory.
+func NewDispatcher(principal security.Principal, config *config.State) (*dispatcher, error) {
+ if err := config.Validate(); err != nil {
+ return nil, fmt.Errorf("invalid config %v: %v", config, err)
+ }
+ uat, err := NewBlessingSystemAssociationStore(config.Root)
+ if err != nil {
+ return nil, fmt.Errorf("cannot create persistent store for identity to system account associations: %v", err)
+ }
+ d := &dispatcher{
+ etag: "default",
+ internal: &internalState{
+ callback: newCallbackState(config.Name),
+ updating: newUpdatingState(),
+ },
+ config: config,
+ uat: uat,
+ locks: make(aclLocks),
+ }
+ // If there exists a signed ACL from a previous instance we prefer that.
+ aclFile, sigFile, _ := d.getACLFilePaths()
+ if _, err := os.Stat(aclFile); err == nil {
+ perm := os.FileMode(0700)
+ data, err := os.OpenFile(aclFile, os.O_RDONLY, perm)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open acl file:%v", err)
+ }
+ defer data.Close()
+ sig, err := os.OpenFile(sigFile, os.O_RDONLY, perm)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open signature file:%v", err)
+ }
+ defer sig.Close()
+ // read and verify the signature of the acl file
+ reader, err := serialization.NewVerifyingReader(data, sig, principal.PublicKey())
+ if err != nil {
+ return nil, fmt.Errorf("failed to read devicemanager ACL file:%v", err)
+ }
+ acl, err := access.ReadTaggedACLMap(reader)
+ if err != nil {
+ return nil, fmt.Errorf("failed to load devicemanager ACL:%v", err)
+ }
+ if err := d.setACL(principal, acl, d.etag, false /* just update etag */); err != nil {
+ return nil, err
+ }
+ } else {
+ if d.auth = vflag.NewAuthorizerOrDie(); d.auth == nil {
+ // If there are no specified ACLs we grant devicemanager
+ // access to all principals until it is claimed.
+ d.auth = allowEveryone{}
+ }
+ }
+ // If we're in 'security agent mode', set up the key manager agent.
+ if len(os.Getenv(agent.FdVarName)) > 0 {
+ if keyMgrAgent, err := keymgr.NewAgent(); err != nil {
+ return nil, fmt.Errorf("NewAgent() failed: %v", err)
+ } else {
+ d.internal.securityAgent = &securityAgentState{
+ keyMgrAgent: keyMgrAgent,
+ }
+ }
+ }
+ return d, nil
+}
+
+func (d *dispatcher) getACLFilePaths() (acl, signature, devicedata string) {
+ devicedata = filepath.Join(d.config.Root, "device-manager", "device-data")
+ acl, signature = filepath.Join(devicedata, "acl.devicemanager"), filepath.Join(devicedata, "acl.signature")
+ return
+}
+
+func (d *dispatcher) claimDeviceManager(principal security.Principal, names []string, proof security.Blessings) error {
+ // TODO(gauthamt): Should we start trusting these identity providers?
+ // TODO(rjkroege): Scrub the state tree of installation and instance ACL files.
+ if len(names) == 0 {
+ vlog.Errorf("No names for claimer(%v) are trusted", proof)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+ principal.BlessingStore().Set(proof, security.AllPrincipals)
+ principal.BlessingStore().SetDefault(proof)
+ // Create ACLs to transfer devicemanager permissions to the provided identity.
+ acl := make(access.TaggedACLMap)
+ for _, n := range names {
+ for _, tag := range access.AllTypicalTags() {
+ acl.Add(security.BlessingPattern(n), string(tag))
+ }
+ }
+ _, etag, err := d.getACL()
+ if err != nil {
+ vlog.Errorf("Failed to getACL:%v", err)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+ if err := d.setACL(principal, acl, etag, true /* store ACL on disk */); err != nil {
+ vlog.Errorf("Failed to setACL:%v", err)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+ return nil
+}
+
+// TODO(rjkroege): Further refactor ACL-setting code.
+func setAppACL(principal security.Principal, locks aclLocks, dir string, acl access.TaggedACLMap, etag string) error {
+ aclpath := path.Join(dir, "acls", "data")
+ sigpath := path.Join(dir, "acls", "signature")
+
+ // Acquire lock. Locks are per path to an acls file.
+ lck, contains := locks[dir]
+ if !contains {
+ lck = new(sync.Mutex)
+ locks[dir] = lck
+ }
+ lck.Lock()
+ defer lck.Unlock()
+
+ f, err := os.Open(aclpath)
+ if err != nil {
+ vlog.Errorf("LoadACL(%s) failed: %v", aclpath, err)
+ return err
+ }
+ defer f.Close()
+
+ curACL, err := access.ReadTaggedACLMap(f)
+ if err != nil {
+ vlog.Errorf("ReadTaggedACLMap(%s) failed: %v", aclpath, err)
+ return err
+ }
+ curEtag, err := computeEtag(curACL)
+ if err != nil {
+ vlog.Errorf("computeEtag failed: %v", err)
+ return err
+ }
+
+ if len(etag) > 0 && etag != curEtag {
+ return verror.Make(access.ErrBadEtag, fmt.Sprintf("etag mismatch in:%s vers:%s", etag, curEtag))
+ }
+
+ return writeACLs(principal, aclpath, sigpath, dir, acl)
+}
+
+func getAppACL(locks aclLocks, dir string) (access.TaggedACLMap, string, error) {
+ aclpath := path.Join(dir, "acls", "data")
+
+ // Acquire lock. Locks are per path to an acls file.
+ lck, contains := locks[dir]
+ if !contains {
+ lck = new(sync.Mutex)
+ locks[dir] = lck
+ }
+ lck.Lock()
+ defer lck.Unlock()
+
+ f, err := os.Open(aclpath)
+ if err != nil {
+ vlog.Errorf("Open(%s) failed: %v", aclpath, err)
+ return nil, "", err
+ }
+ defer f.Close()
+
+ acl, err := access.ReadTaggedACLMap(f)
+ if err != nil {
+ vlog.Errorf("ReadTaggedACLMap(%s) failed: %v", aclpath, err)
+ return nil, "", err
+ }
+ curEtag, err := computeEtag(acl)
+ if err != nil {
+ return nil, "", err
+ }
+ return acl, curEtag, nil
+}
+
+func computeEtag(acl access.TaggedACLMap) (string, error) {
+ b := new(bytes.Buffer)
+ if err := acl.WriteTo(b); err != nil {
+ vlog.Errorf("Failed to save ACL:%v", err)
+ return "", err
+ }
+ // Update the acl/etag/authorizer for this dispatcher
+ md5hash := md5.Sum(b.Bytes())
+ etag := hex.EncodeToString(md5hash[:])
+ return etag, nil
+}
+
+func writeACLs(principal security.Principal, aclFile, sigFile, dir string, acl access.TaggedACLMap) error {
+ // Create dir directory if it does not exist
+ os.MkdirAll(dir, os.FileMode(0700))
+ // Save the object to temporary data and signature files, and then move
+ // those files to the actual data and signature file.
+ data, err := ioutil.TempFile(dir, "data")
+ if err != nil {
+ vlog.Errorf("Failed to open tmpfile data:%v", err)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+ defer os.Remove(data.Name())
+ sig, err := ioutil.TempFile(dir, "sig")
+ if err != nil {
+ vlog.Errorf("Failed to open tmpfile sig:%v", err)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+ defer os.Remove(sig.Name())
+ writer, err := serialization.NewSigningWriteCloser(data, sig, principal, nil)
+ if err != nil {
+ vlog.Errorf("Failed to create NewSigningWriteCloser:%v", err)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+ if err = acl.WriteTo(writer); err != nil {
+ vlog.Errorf("Failed to SaveACL:%v", err)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+ if err = writer.Close(); err != nil {
+ vlog.Errorf("Failed to Close() SigningWriteCloser:%v", err)
+ return verror2.Make(ErrOperationFailed, nil)
+ }
+ if err := os.Rename(data.Name(), aclFile); err != nil {
+ return err
+ }
+ if err := os.Rename(sig.Name(), sigFile); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (d *dispatcher) setACL(principal security.Principal, acl access.TaggedACLMap, etag string, writeToFile bool) error {
+ d.mu.Lock()
+ defer d.mu.Unlock()
+ aclFile, sigFile, devicedata := d.getACLFilePaths()
+
+ if len(etag) > 0 && etag != d.etag {
+ return verror.Make(access.ErrBadEtag, fmt.Sprintf("etag mismatch in:%s vers:%s", etag, d.etag))
+ }
+ if writeToFile {
+ if err := writeACLs(principal, aclFile, sigFile, devicedata, acl); err != nil {
+ return err
+ }
+ }
+
+ etag, err := computeEtag(acl)
+ if err != nil {
+ return err
+ }
+ auth, err := access.TaggedACLAuthorizer(acl, access.TypicalTagType())
+ if err != nil {
+ return err
+ }
+ d.acl, d.etag, d.auth = acl, etag, auth
+ return nil
+}
+
+func (d *dispatcher) getACL() (acl access.TaggedACLMap, etag string, err error) {
+ d.mu.RLock()
+ defer d.mu.RUnlock()
+ return d.acl, d.etag, nil
+}
+
+// DISPATCHER INTERFACE IMPLEMENTATION
+func (d *dispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) {
+ components := strings.Split(suffix, "/")
+ for i := 0; i < len(components); i++ {
+ if len(components[i]) == 0 {
+ components = append(components[:i], components[i+1:]...)
+ i--
+ }
+ }
+ if len(components) == 0 {
+ return ipc.ChildrenGlobberInvoker(deviceSuffix, appsSuffix), d.auth, nil
+ }
+ // The implementation of the device manager is split up into several
+ // invokers, which are instantiated depending on the receiver name
+ // prefix.
+ switch components[0] {
+ case deviceSuffix:
+ receiver := device.DeviceServer(&deviceService{
+ callback: d.internal.callback,
+ updating: d.internal.updating,
+ config: d.config,
+ disp: d,
+ uat: d.uat,
+ })
+ return receiver, d.auth, nil
+ case appsSuffix:
+ // Requests to apps/*/*/*/logs are handled locally by LogFileService.
+ // Requests to apps/*/*/*/pprof are proxied to the apps' __debug/pprof object.
+ // Requests to apps/*/*/*/stats are proxied to the apps' __debug/stats object.
+ // Everything else is handled by the Application server.
+ if len(components) >= 5 {
+ appInstanceDir, err := instanceDir(d.config.Root, components[1:4])
+ if err != nil {
+ return nil, nil, err
+ }
+ switch kind := components[4]; kind {
+ case "logs":
+ logsDir := filepath.Join(appInstanceDir, "logs")
+ suffix := naming.Join(components[5:]...)
+ return logsimpl.NewLogFileService(logsDir, suffix), d.auth, nil
+ case "pprof", "stats":
+ info, err := loadInstanceInfo(appInstanceDir)
+ if err != nil {
+ return nil, nil, err
+ }
+ if !instanceStateIs(appInstanceDir, started) {
+ return nil, nil, verror2.Make(ErrInvalidSuffix, nil)
+ }
+ var sigStub signatureStub
+ if kind == "pprof" {
+ sigStub = pprof.PProfServer(nil)
+ } else {
+ sigStub = stats.StatsServer(nil)
+ }
+ suffix := naming.Join("__debug", naming.Join(components[4:]...))
+ remote := naming.JoinAddressName(info.AppCycleMgrName, suffix)
+ invoker := &proxyInvoker{
+ remote: remote,
+ access: access.Debug,
+ sigStub: sigStub,
+ }
+ return invoker, d.auth, nil
+ }
+ }
+ deviceACLs, _, err := d.getACL()
+ if err != nil {
+ return nil, nil, err
+ }
+ receiver := device.ApplicationServer(&appService{
+ callback: d.internal.callback,
+ config: d.config,
+ suffix: components[1:],
+ uat: d.uat,
+ locks: d.locks,
+ deviceACL: deviceACLs,
+ securityAgent: d.internal.securityAgent,
+ })
+ appSpecificAuthorizer, err := newAppSpecificAuthorizer(d.auth, d.config, components[1:])
+ if err != nil {
+ return nil, nil, err
+ }
+ return receiver, appSpecificAuthorizer, nil
+ case configSuffix:
+ if len(components) != 2 {
+ return nil, nil, verror2.Make(ErrInvalidSuffix, nil)
+ }
+ receiver := idevice.ConfigServer(&configService{
+ callback: d.internal.callback,
+ suffix: components[1],
+ })
+ // The nil authorizer ensures that only principals blessed by
+ // the device manager can talk back to it. All apps started by
+ // the device manager should fall in that category.
+ //
+ // TODO(caprita,rjkroege): We should further refine this, by
+ // only allowing the app to update state referring to itself
+ // (and not other apps).
+ return receiver, nil, nil
+ default:
+ return nil, nil, verror2.Make(ErrInvalidSuffix, nil)
+ }
+}
+
+func newAppSpecificAuthorizer(sec security.Authorizer, config *config.State, suffix []string) (security.Authorizer, error) {
+ // TODO(rjkroege): This does not support <appname>.Start() to start all
+ // instances. Correct this.
+
+ // If we are attempting a method invocation against "apps/", we use the
+ // device-manager wide ACL.
+ if len(suffix) == 0 || len(suffix) == 1 {
+ return sec, nil
+ }
+ // Otherwise, we require a per-installation and per-instance ACL file.
+ if len(suffix) == 2 {
+ p, err := installationDirCore(suffix, config.Root)
+ if err != nil {
+ vlog.Errorf("newAppSpecificAuthorizer failed: %v", err)
+ return nil, err
+ }
+ return access.TaggedACLAuthorizerFromFile(path.Join(p, "acls", "data"), access.TypicalTagType())
+ }
+ if len(suffix) > 2 {
+ p, err := instanceDir(config.Root, suffix[0:3])
+ if err != nil {
+ vlog.Errorf("newAppSpecificAuthorizer failed: %v", err)
+ return nil, err
+ }
+ return access.TaggedACLAuthorizerFromFile(path.Join(p, "acls", "data"), access.TypicalTagType())
+ }
+ return nil, verror2.Make(ErrInvalidSuffix, nil)
+}
+
+// allowEveryone implements the authorization policy that allows all principals
+// access.
+type allowEveryone struct{}
+
+func (allowEveryone) Authorize(security.Context) error { return nil }