blob: 93f09260e8f6889e5b5ccb13edcf4bba9fa1ba8d [file] [log] [blame]
package mounttable
import (
"encoding/json"
"fmt"
"os"
"reflect"
"strings"
"sync"
"time"
"v.io/core/veyron/lib/glob"
"v.io/core/veyron2"
"v.io/core/veyron2/ipc"
"v.io/core/veyron2/naming"
"v.io/core/veyron2/security"
"v.io/core/veyron2/services/mounttable"
"v.io/core/veyron2/services/security/access"
"v.io/core/veyron2/verror2"
"v.io/core/veyron2/vlog"
)
var (
errNamingLoop = verror2.Register("v.io/core/veyron/services/mountable/lib", verror2.NoRetry, "Loop in namespace")
traverseTags = []mounttable.Tag{mounttable.Read, mounttable.Resolve, mounttable.Create, mounttable.Admin}
createTags = []mounttable.Tag{mounttable.Create, mounttable.Admin}
removeTags = []mounttable.Tag{mounttable.Admin}
mountTags = []mounttable.Tag{mounttable.Mount, mounttable.Admin}
globTags = []mounttable.Tag{mounttable.Read, mounttable.Admin}
setTags = []mounttable.Tag{mounttable.Admin}
getTags = []mounttable.Tag{mounttable.Admin, mounttable.Read}
allTags = []mounttable.Tag{mounttable.Read, mounttable.Resolve, mounttable.Admin, mounttable.Mount, mounttable.Create}
)
// mountTable represents a namespace. One exists per server instance.
type mountTable struct {
root *node
superUsers access.ACL
}
var _ ipc.Dispatcher = (*mountTable)(nil)
// mountContext represents a client bind. The name is the name that was bound to.
type mountContext struct {
name string
elems []string // parsed elements of name
mt *mountTable
}
// mount represents a single mount point. It contains the rooted names of all servers mounted
// here. The servers are considered equivalent, i.e., RPCs to a name below this
// point can be sent to any of these servers.
type mount struct {
servers *serverList
mt bool
}
// node is a single point in the tree representing the mount table.
type node struct {
sync.RWMutex
parent *node
mount *mount
children map[string]*node
acls *TAMG
amTemplate access.TaggedACLMap
explicitACLs bool
}
const templateVar = "%%"
// NewMountTableDispatcher creates a new server that uses the ACLs specified in
// aclfile for authorization.
//
// aclfile is a JSON-encoded mapping from paths in the mounttable to the
// access.TaggedACLMap for that path. The tags used in the map are the typical
// access tags (the Tag type defined in veyron2/services/security/access).
func NewMountTableDispatcher(aclfile string) (ipc.Dispatcher, error) {
mt := &mountTable{
root: new(node),
}
mt.root.parent = new(node) // just for its lock
if err := mt.parseACLs(aclfile); err != nil {
return nil, err
}
return mt, nil
}
func (mt *mountTable) parseACLs(path string) error {
vlog.VI(2).Infof("parseACLs(%s)", path)
if path == "" {
return nil
}
var tams map[string]access.TaggedACLMap
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
if err = json.NewDecoder(f).Decode(&tams); err != nil {
return err
}
for name, tam := range tams {
var elems []string
isPattern := false
// Create name and add the ACL map to it.
if len(name) == 0 {
// If the config file has is an Admin tag on the root ACL, the
// list of Admin users is the equivalent of a super user for
// the whole table. This is not updated if the ACL is later
// modified.
if acl, exists := tam[string(mounttable.Admin)]; exists {
mt.superUsers = acl
}
} else {
// ACL templates terminate with a %% element. These are very
// constrained matches, i.e., the trailing element of the name
// is copied into every %% in the ACL.
elems = strings.Split(name, "/")
if elems[len(elems)-1] == templateVar {
isPattern = true
elems = elems[:len(elems)-1]
}
}
n, err := mt.findNode(nil, elems, true, nil)
if n != nil || err == nil {
vlog.VI(2).Infof("added tam %v to %s", tam, name)
if isPattern {
n.amTemplate = tam
} else {
n.acls, _ = n.acls.Set("", tam)
n.explicitACLs = true
}
}
n.parent.Unlock()
n.Unlock()
}
return nil
}
// Lookup implements ipc.Dispatcher.Lookup.
func (mt *mountTable) Lookup(name string) (interface{}, security.Authorizer, error) {
vlog.VI(2).Infof("*********************Lookup %s", name)
ms := &mountContext{
name: name,
mt: mt,
}
if len(name) > 0 {
ms.elems = strings.Split(name, "/")
}
return mounttable.MountTableServer(ms), ms, nil
}
// isActive returns true if a mount has unexpired servers attached.
func (m *mount) isActive() bool {
if m == nil {
return false
}
return m.servers.removeExpired() > 0
}
// satisfies returns no error if the ctx + n.acls satisfies the associated one of the required Tags.
func (n *node) satisfies(mt *mountTable, ctx ipc.ServerContext, tags []mounttable.Tag) error {
// No ACLs means everything (for now).
if ctx == nil || tags == nil || n.acls == nil {
return nil
}
// "Self-RPCs" are always authorized.
if l, r := ctx.LocalBlessings(), ctx.RemoteBlessings(); l != nil && r != nil && reflect.DeepEqual(l.PublicKey(), r.PublicKey()) {
return nil
}
// Match client's blessings against the ACLs.
var blessings []string
if ctx.RemoteBlessings() != nil {
blessings = ctx.RemoteBlessings().ForContext(ctx)
}
for _, tag := range tags {
if acl, exists := n.acls.GetACLForTag(string(tag)); exists && acl.Includes(blessings...) {
return nil
}
}
if mt.superUsers.Includes(blessings...) {
return nil
}
return fmt.Errorf("%v does not match ACL", blessings)
}
func expand(acl *access.ACL, name string) *access.ACL {
newACL := new(access.ACL)
for _, bp := range acl.In {
newACL.In = append(newACL.In, security.BlessingPattern(strings.Replace(string(bp), templateVar, name, -1)))
}
for _, bp := range acl.NotIn {
newACL.NotIn = append(newACL.NotIn, strings.Replace(bp, templateVar, name, -1))
}
return newACL
}
// satisfiesTemplate returns no error if the ctx + n.amTemplate satisfies the associated one of
// the required Tags.
func (n *node) satisfiesTemplate(ctx ipc.ServerContext, tags []mounttable.Tag, name string) error {
if n.amTemplate == nil {
return nil
}
// Match client's blessings against the ACLs.
var blessings []string
if ctx.RemoteBlessings() != nil {
blessings = ctx.RemoteBlessings().ForContext(ctx)
}
for _, tag := range tags {
if acl, exists := n.amTemplate[string(tag)]; exists && expand(&acl, name).Includes(blessings...) {
return nil
}
}
return fmt.Errorf("%v does not match ACL", blessings)
}
// copyACLs copies one nodes ACLs to another and adds the clients blessings as
// patterns to the Admin tag.
func copyACLs(ctx ipc.ServerContext, cur *node) *TAMG {
if ctx == nil {
return nil
}
if cur.acls == nil {
return nil
}
acls := cur.acls.Copy()
var blessings []string
if ctx.RemoteBlessings() != nil {
blessings = ctx.RemoteBlessings().ForContext(ctx)
}
for _, b := range blessings {
acls.Add(security.BlessingPattern(b), string(mounttable.Admin))
}
return acls
}
// createTAMGFromTemplate creates a new TAMG from the template subsituting name for %% everywhere.
func createTAMGFromTemplate(tam access.TaggedACLMap, name string) *TAMG {
tamg := NewTAMG()
for tag, acl := range tam {
tamg.tam[tag] = *expand(&acl, name)
}
return tamg
}
// traverse returns the node for the path represented by elems. If none exists and create is false, return nil.
// Otherwise create the path and return a pointer to the terminal node. If a mount point is encountered
// while following the path, return that node and any remaining elems.
//
// If it returns a node, both the node and its parent are locked.
func (mt *mountTable) traverse(ctx ipc.ServerContext, elems []string, create bool) (*node, []string, error) {
// Invariant is that the current node and its parent are both locked.
cur := mt.root
cur.parent.Lock()
cur.Lock()
for i, e := range elems {
vlog.VI(2).Infof("satisfying %v %v", elems[0:i], *cur)
if ctx != nil {
if err := cur.satisfies(mt, ctx, traverseTags); err != nil {
cur.parent.Unlock()
cur.Unlock()
return nil, nil, err
}
}
// If we hit another mount table, we're done.
if cur.mount.isActive() {
return cur, elems[i:], nil
}
// Walk the children looking for a match.
c, ok := cur.children[e]
if ok {
cur.parent.Unlock()
cur = c
cur.Lock()
continue
}
if !create {
cur.parent.Unlock()
cur.Unlock()
return nil, nil, nil
}
// Create a new node and keep recursing.
cur.parent.Unlock()
if err := cur.satisfies(mt, ctx, createTags); err != nil {
cur.Unlock()
return nil, nil, err
}
if ctx != nil {
if err := cur.satisfiesTemplate(ctx, createTags, e); err != nil {
cur.Unlock()
return nil, nil, err
}
}
// At this point cur is still locked, OK to use and change it.
next := new(node)
next.parent = cur
if cur.amTemplate != nil {
next.acls = createTAMGFromTemplate(cur.amTemplate, e)
} else {
next.acls = copyACLs(ctx, cur)
}
if cur.children == nil {
cur.children = make(map[string]*node)
}
cur.children[e] = next
cur = next
cur.Lock()
}
// Only way out of the loop is via a return or exhausting all elements. In
// the latter case both cur and cur.parent are locked.
return cur, nil, nil
}
// findNode finds a node in the table and optionally creates a path to it.
//
// If a node is found, on return it and its parent are locked.
func (mt *mountTable) findNode(ctx ipc.ServerContext, elems []string, create bool, tags []mounttable.Tag) (*node, error) {
n, nelems, err := mt.traverse(ctx, elems, create)
if err != nil {
return nil, err
}
if n == nil {
return nil, nil
}
if len(nelems) > 0 {
n.parent.Unlock()
n.Unlock()
return nil, nil
}
if err := n.satisfies(mt, ctx, tags); err != nil {
n.parent.Unlock()
n.Unlock()
return nil, err
}
return n, nil
}
// findMountPoint returns the first mount point encountered in the path and
// any elements remaining of the path.
//
// If a mountpoint is found, on return it and its parent are locked.
func (mt *mountTable) findMountPoint(ctx ipc.ServerContext, elems []string) (*node, []string, error) {
n, nelems, err := mt.traverse(ctx, elems, false)
if err != nil {
return nil, nil, err
}
if n == nil {
return nil, nil, nil
}
if !n.mount.isActive() {
removed := n.removeUseless()
n.parent.Unlock()
n.Unlock()
// If we removed the node, see if we can remove any of its
// ascendants.
if removed {
mt.removeUselessRecursive(elems[:len(elems)-1])
}
return nil, nil, nil
}
return n, nelems, nil
}
// Authorize verifies that the client has access to the requested node.
// Since we do the check at the time of access, we always return OK here.
func (ms *mountContext) Authorize(ctx security.Context) error {
return nil
}
// ResolveStep returns the next server in a resolution, the name remaining below that server,
// and whether or not that server is another mount table.
func (ms *mountContext) ResolveStepX(ctx ipc.ServerContext) (entry naming.VDLMountEntry, err error) {
return ms.ResolveStep(ctx)
}
// ResolveStep returns the next server in a resolution in the form of a MountEntry. The name
// in the mount entry is the name relative to the server's root.
func (ms *mountContext) ResolveStep(ctx ipc.ServerContext) (entry naming.VDLMountEntry, err error) {
vlog.VI(2).Infof("ResolveStep %q", ms.name)
mt := ms.mt
// Find the next mount point for the name.
n, elems, werr := mt.findMountPoint(ctx, ms.elems)
if werr != nil {
err = werr
return
}
if n == nil {
entry.Name = ms.name
if len(ms.elems) == 0 {
err = verror2.Make(naming.ErrNoSuchNameRoot, ctx.Context(), ms.name)
} else {
err = verror2.Make(naming.ErrNoSuchName, ctx.Context(), ms.name)
}
return
}
n.parent.Unlock()
defer n.Unlock()
entry.Servers = n.mount.servers.copyToSlice()
entry.Name = strings.Join(elems, "/")
entry.MT = n.mount.mt
return
}
func hasMTFlag(flags naming.MountFlag) bool {
return (flags & naming.MT) == naming.MT
}
func hasReplaceFlag(flags naming.MountFlag) bool {
return (flags & naming.Replace) == naming.Replace
}
// Mount a server onto the name in the receiver.
func (ms *mountContext) Mount(ctx ipc.ServerContext, server string, ttlsecs uint32, flags naming.MountFlag) error {
return ms.MountX(ctx, server, nil, ttlsecs, flags)
}
func (ms *mountContext) MountX(ctx ipc.ServerContext, server string, patterns []security.BlessingPattern, ttlsecs uint32, flags naming.MountFlag) error {
if len(patterns) == 0 {
// No patterns provided in the request, take the conservative
// approach and assume that the server being mounted will
// present the same blessings as the client calling Mount.
for _, b := range ctx.RemoteBlessings().ForContext(ctx) {
patterns = append(patterns, security.BlessingPattern(b))
}
}
mt := ms.mt
if ttlsecs == 0 {
ttlsecs = 10 * 365 * 24 * 60 * 60 // a really long time
}
vlog.VI(2).Infof("*********************Mount %q -> %v%s", ms.name, patterns, server)
// Make sure the server address is reasonable.
epString := server
if naming.Rooted(server) {
epString, _ = naming.SplitAddressName(server)
}
_, err := veyron2.NewEndpoint(epString)
if err != nil {
return fmt.Errorf("malformed address %q for mounted server %q", epString, server)
}
// Find/create node in namespace and add the mount.
n, werr := mt.findNode(ctx, ms.elems, true, mountTags)
if werr != nil {
return werr
}
if n == nil {
return verror2.Make(naming.ErrNoSuchNameRoot, ctx.Context(), ms.name)
}
// We don't need the parent lock
n.parent.Unlock()
defer n.Unlock()
if hasReplaceFlag(flags) {
n.mount = nil
}
wantMT := hasMTFlag(flags)
if n.mount == nil {
n.mount = &mount{servers: newServerList(), mt: wantMT}
} else {
if wantMT != n.mount.mt {
return fmt.Errorf("MT doesn't match")
}
}
n.mount.servers.add(server, patterns, time.Duration(ttlsecs)*time.Second)
return nil
}
// fullName is for debugging only and should not normally be called.
func (n *node) fullName() string {
if n.parent == nil || n.parent.parent == nil {
return ""
}
for k, c := range n.parent.children {
if c == n {
return n.parent.fullName() + "/" + k
}
}
return n.parent.fullName() + "/" + "?"
}
// removeUseless removes a node and all of its ascendants that are not useful.
//
// We assume both n and n.parent are locked.
func (n *node) removeUseless() bool {
if len(n.children) > 0 || n.mount.isActive() || n.explicitACLs {
return false
}
for k, c := range n.parent.children {
if c == n {
delete(n.parent.children, k)
break
}
}
return true
}
// removeUselessRecursive removes any useless nodes on the tail of the path.
func (mt *mountTable) removeUselessRecursive(elems []string) {
for i := len(elems); i > 0; i-- {
n, nelems, _ := mt.traverse(nil, elems[:i-1], false)
if n == nil {
break
}
if nelems != nil {
n.parent.Unlock()
n.Unlock()
break
}
removed := n.removeUseless()
n.parent.Unlock()
n.Unlock()
if !removed {
break
}
}
}
// Unmount removes servers from the name in the receiver. If server is specified, only that
// server is removed.
func (ms *mountContext) Unmount(ctx ipc.ServerContext, server string) error {
vlog.VI(2).Infof("*********************Unmount %q, %s", ms.name, server)
mt := ms.mt
n, err := mt.findNode(ctx, ms.elems, false, mountTags)
if err != nil {
return err
}
if n == nil {
return nil
}
if server == "" {
n.mount = nil
} else if n.mount != nil && n.mount.servers.remove(server) == 0 {
n.mount = nil
}
removed := n.removeUseless()
n.parent.Unlock()
n.Unlock()
if removed {
// If we removed the node, see if we can also remove
// any of its ascendants.
mt.removeUselessRecursive(ms.elems[:len(ms.elems)-1])
}
return nil
}
// Delete removes the receiver. If all is true, any subtree is also removed.
func (ms *mountContext) Delete(ctx ipc.ServerContext, deleteSubTree bool) error {
vlog.VI(2).Infof("*********************Delete %q, %v", ms.name, deleteSubTree)
if len(ms.elems) == 0 {
// We can't delete the root.
return fmt.Errorf("cannot delete root node")
}
mt := ms.mt
// Find and lock the parent node.
n, err := mt.findNode(ctx, ms.elems, false, removeTags)
if err != nil {
return err
}
if n == nil {
return nil
}
defer n.parent.Unlock()
defer n.Unlock()
if !deleteSubTree && len(n.children) > 0 {
return fmt.Errorf("cannot delete %s: has children", ms.name)
}
delete(n.parent.children, ms.elems[len(ms.elems)-1])
return nil
}
// A struct holding a partial result of Glob.
type globEntry struct {
n *node
name string
}
// globStep is called with n and n.parent locked. Returns with both unlocked.
func (mt *mountTable) globStep(n *node, name string, pattern *glob.Glob, ctx ipc.ServerContext, ch chan<- naming.VDLMountEntry) {
vlog.VI(2).Infof("globStep(%s, %s)", name, pattern)
// If this is a mount point, we're done.
if m := n.mount; m != nil {
removed := n.removeUseless()
if removed {
n.parent.Unlock()
n.Unlock()
return
}
// Don't need the parent lock anymore.
n.parent.Unlock()
me := naming.VDLMountEntry{
Name: name,
Servers: m.servers.copyToSlice(),
MT: n.mount.mt,
}
// Unlock while we are sending on the channel to avoid livelock.
n.Unlock()
ch <- me
return
}
if !pattern.Finished() {
// Recurse through the children. OK if client has read access to the
// directory or has traverse access to the directory and any access to the child.
allAllowed := true
if err := n.satisfies(mt, ctx, globTags); err != nil {
allAllowed = false
if err := n.satisfies(mt, ctx, traverseTags); err != nil {
n.parent.Unlock()
n.Unlock()
return
}
}
children := make(map[string]*node, len(n.children))
for k, c := range n.children {
children[k] = c
}
n.parent.Unlock()
for k, c := range children {
// At this point, n lock is held.
if ok, _, suffix := pattern.MatchInitialSegment(k); ok {
c.Lock()
if !allAllowed {
// If child allows any access show it. Otherwise, skip.
if err := c.satisfies(mt, ctx, allTags); err != nil {
c.Unlock()
continue
}
}
mt.globStep(c, naming.Join(name, k), suffix, ctx, ch)
n.Lock()
}
}
// Relock the node and its parent in the correct order.
// Safe to access n.parent when its unlocked because it never changes.
n.Unlock()
n.parent.Lock()
n.Lock()
}
// Remove if no longer useful.
if n.removeUseless() || pattern.Len() != 0 {
n.parent.Unlock()
n.Unlock()
return
}
// To see anything, one has to have some access to the node. Don't need the parent lock anymore.
n.parent.Unlock()
if err := n.satisfies(mt, ctx, allTags); err != nil {
n.Unlock()
return
}
// Unlock while we are sending on the channel to avoid livelock.
n.Unlock()
ch <- naming.VDLMountEntry{Name: name}
}
// Glob finds matches in the namespace. If we reach a mount point before matching the
// whole pattern, return that mount point.
//
// pattern is a glob pattern as defined by the veyron/lib/glob package.
//
// To avoid livelocking an application, Glob grabs and releases locks as it descends the tree
// and holds no locks while writing to the channel. As such a glob can interleave with other
// operations that add or remove nodes. The result returned by glob may, therefore, represent
// a state that never existed in the mounttable. For example, if someone removes c/d and later
// adds a/b while a Glob is in progress, the Glob may return a set of nodes that includes both
// c/d and a/b.
func (ms *mountContext) Glob__(ctx ipc.ServerContext, pattern string) (<-chan naming.VDLMountEntry, error) {
vlog.VI(2).Infof("mt.Glob %v", ms.elems)
g, err := glob.Parse(pattern)
if err != nil {
return nil, err
}
mt := ms.mt
ch := make(chan naming.VDLMountEntry)
go func() {
defer close(ch)
// If there was an access error, just ignore the entry, i.e., make it invisible.
n, err := mt.findNode(ctx, ms.elems, false, nil)
if err != nil {
return
}
// If the current name is not fully resolvable on this nameserver we
// don't need to evaluate the glob expression. Send a partially resolved
// name back to the client.
if n == nil {
ms.linkToLeaf(ctx, ch)
return
}
mt.globStep(n, "", g, ctx, ch)
}()
return ch, nil
}
func (ms *mountContext) linkToLeaf(ctx ipc.ServerContext, ch chan<- naming.VDLMountEntry) {
n, elems, err := ms.mt.findMountPoint(ctx, ms.elems)
if err != nil || n == nil {
return
}
n.parent.Unlock()
servers := n.mount.servers.copyToSlice()
for i, s := range servers {
servers[i].Server = naming.Join(s.Server, strings.Join(elems, "/"))
}
n.Unlock()
ch <- naming.VDLMountEntry{Name: "", Servers: servers}
}
func (ms *mountContext) SetACL(ctx ipc.ServerContext, tam access.TaggedACLMap, etag string) error {
vlog.VI(2).Infof("SetACL %q", ms.name)
mt := ms.mt
// Find/create node in namespace and add the mount.
n, err := mt.findNode(ctx, ms.elems, true, setTags)
if err != nil {
return err
}
if n == nil {
// TODO(p): can this even happen?
return verror2.Make(naming.ErrNoSuchName, ctx.Context(), ms.name)
}
n.parent.Unlock()
defer n.Unlock()
n.acls, err = n.acls.Set(etag, tam)
if err == nil {
n.explicitACLs = true
}
return err
}
func (ms *mountContext) GetACL(ctx ipc.ServerContext) (access.TaggedACLMap, string, error) {
vlog.VI(2).Infof("GetACL %q", ms.name)
mt := ms.mt
// Find node in namespace and add the mount.
n, err := mt.findNode(ctx, ms.elems, false, getTags)
if err != nil {
return nil, "", err
}
if n == nil {
return nil, "", verror2.Make(naming.ErrNoSuchName, ctx.Context(), ms.name)
}
n.parent.Unlock()
defer n.Unlock()
etag, tam := n.acls.Get()
return tam, etag, nil
}