mounttablelib: Persist permissions to the underlying file system.
I also got rid of references to TAM's (tagged acl maps). Also
changed acl to al everywhere I noticed since we don't have
access control lists, we have access lists.
The al's are stored as JSON to make them easy to read and also
edit if need be.
MultiPart: 1/3
TBR: Opps, I submitted the other two... Doing this one quickly.
Change-Id: Ia423f123f4ef4c9d3e641e01c8656148ad2208c1
diff --git a/services/device/internal/starter/starter.go b/services/device/internal/starter/starter.go
index 9ea7dba..a28fe26 100644
--- a/services/device/internal/starter/starter.go
+++ b/services/device/internal/starter/starter.go
@@ -43,6 +43,7 @@
Name string // Name to publish the mounttable service under.
ListenSpec rpc.ListenSpec // ListenSpec for the server.
PermissionsFile string // Path to the Permissions file used by the mounttable.
+ PersistenceDir string // Path to the directory holding persistent acls.
// Name in the local neighborhood on which to make the mounttable
// visible. If empty, the mounttable will not be visible in the local
// neighborhood.
@@ -304,7 +305,7 @@
}
func startMounttable(ctx *context.T, n NamespaceArgs) (string, func(), error) {
- mtName, stopMT, err := mounttablelib.StartServers(ctx, n.ListenSpec, n.Name, n.Neighborhood, n.PermissionsFile, "mounttable")
+ mtName, stopMT, err := mounttablelib.StartServers(ctx, n.ListenSpec, n.Name, n.Neighborhood, n.PermissionsFile, n.PersistenceDir, "mounttable")
if err != nil {
vlog.Errorf("mounttablelib.StartServers(%#v) failed: %v", n, err)
} else {
diff --git a/services/internal/servicetest/modules.go b/services/internal/servicetest/modules.go
index 5d43db5..13b14a0 100644
--- a/services/internal/servicetest/modules.go
+++ b/services/internal/servicetest/modules.go
@@ -47,7 +47,7 @@
if err != nil {
return fmt.Errorf("root failed: %v", err)
}
- mt, err := mounttablelib.NewMountTableDispatcher("", "mounttable")
+ mt, err := mounttablelib.NewMountTableDispatcher("", "", "mounttable")
if err != nil {
return fmt.Errorf("mounttablelib.NewMountTableDispatcher failed: %s", err)
}
diff --git a/services/mounttable/mounttabled/mounttable.go b/services/mounttable/mounttabled/mounttable.go
index d417726..a95f32a 100644
--- a/services/mounttable/mounttabled/mounttable.go
+++ b/services/mounttable/mounttabled/mounttable.go
@@ -19,16 +19,17 @@
)
var (
- mountName = flag.String("name", "", `<name>, if provided, causes the mount table to mount itself under that name. The name may be absolute for a remote mount table service (e.g., "/<remote mt address>//some/suffix") or could be relative to this process' default mount table (e.g., "some/suffix").`)
- aclFile = flag.String("acls", "", "AccessList file. Default is to allow all access.")
- nhName = flag.String("neighborhood-name", "", `<nh name>, if provided, will enable sharing with the local neighborhood with the provided name. The address of this mounttable will be published to the neighboorhood and everything in the neighborhood will be visible on this mounttable.`)
+ mountName = flag.String("name", "", `<name>, if provided, causes the mount table to mount itself under that name. The name may be absolute for a remote mount table service (e.g., "/<remote mt address>//some/suffix") or could be relative to this process' default mount table (e.g., "some/suffix").`)
+ aclFile = flag.String("acls", "", "AccessList file. Default is to allow all access.")
+ nhName = flag.String("neighborhood-name", "", `<nh name>, if provided, will enable sharing with the local neighborhood with the provided name. The address of this mounttable will be published to the neighboorhood and everything in the neighborhood will be visible on this mounttable.`)
+ persistDir = flag.String("persist-dir", "", `Directory in which to persist permissions.`)
)
func main() {
ctx, shutdown := v23.Init()
defer shutdown()
- name, stop, err := mounttablelib.StartServers(ctx, v23.GetListenSpec(ctx), *mountName, *nhName, *aclFile, "mounttable")
+ name, stop, err := mounttablelib.StartServers(ctx, v23.GetListenSpec(ctx), *mountName, *nhName, *aclFile, *persistDir, "mounttable")
if err != nil {
vlog.Errorf("mounttablelib.StartServers failed: %v", err)
os.Exit(1)
diff --git a/services/mounttable/mounttablelib/mounttable.go b/services/mounttable/mounttablelib/mounttable.go
index f3359ca..ecf1c1f 100644
--- a/services/mounttable/mounttablelib/mounttable.go
+++ b/services/mounttable/mounttablelib/mounttable.go
@@ -6,7 +6,6 @@
package mounttablelib
import (
- "encoding/json"
"os"
"reflect"
"strings"
@@ -51,10 +50,18 @@
allTags = []mounttable.Tag{mounttable.Read, mounttable.Resolve, mounttable.Admin, mounttable.Mount, mounttable.Create}
)
+type persistence interface {
+ persistPerms(name string, perm *VersionedPermissions) error
+ persistDelete(name string) error
+ close()
+}
+
// mountTable represents a namespace. One exists per server instance.
type mountTable struct {
root *node
superUsers access.AccessList
+ persisting bool
+ persist persistence
nodeCounter *stats.Integer
serverCounter *stats.Integer
}
@@ -83,29 +90,35 @@
parent *node
mount *mount
children map[string]*node
- acls *TAMG
- amTemplate access.Permissions
- explicitAccessLists bool
+ vPerms *VersionedPermissions
+ permsTemplate access.Permissions
+ explicitPermissions bool
}
const templateVar = "%%"
// NewMountTableDispatcher creates a new server that uses the AccessLists specified in
-// aclfile for authorization.
+// permissions file for authorization.
//
-// aclfile is a JSON-encoded mapping from paths in the mounttable to the
+// permsFile is a JSON-encoded mapping from paths in the mounttable to the
// access.Permissions for that path. The tags used in the map are the typical
// access tags (the Tag type defined in v.io/v23/security/access).
//
+// persistDir is the directory for persisting Permissions.
+//
// statsPrefix is the prefix for for exported statistics objects.
-func NewMountTableDispatcher(aclfile, statsPrefix string) (rpc.Dispatcher, error) {
+func NewMountTableDispatcher(permsFile, persistDir, statsPrefix string) (rpc.Dispatcher, error) {
mt := &mountTable{
root: new(node),
nodeCounter: stats.NewInteger(naming.Join(statsPrefix, "num-nodes")),
serverCounter: stats.NewInteger(naming.Join(statsPrefix, "num-mounted-servers")),
}
mt.root.parent = mt.newNode() // just for its lock
- if err := mt.parseAccessLists(aclfile); err != nil && !os.IsNotExist(err) {
+ if persistDir != "" {
+ mt.persist = newPersistentStore(mt, persistDir)
+ mt.persisting = mt.persist != nil
+ }
+ if err := mt.parsePermFile(permsFile); err != nil && !os.IsNotExist(err) {
return nil, err
}
return mt, nil
@@ -146,59 +159,6 @@
delete(parent.children, child)
}
-func (mt *mountTable) parseAccessLists(path string) error {
- vlog.VI(2).Infof("parseAccessLists(%s)", path)
- if path == "" {
- return nil
- }
- var tams map[string]access.Permissions
- 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 AccessList map to it.
- if len(name) == 0 {
- // If the config file has is an Admin tag on the root AccessList, the
- // list of Admin users is the equivalent of a super user for
- // the whole table. This is not updated if the AccessList is later
- // modified.
- if acl, exists := tam[string(mounttable.Admin)]; exists {
- mt.superUsers = acl
- }
- } else {
- // AccessList templates terminate with a %% element. These are very
- // constrained matches, i.e., the trailing element of the name
- // is copied into every %% in the AccessList.
- elems = strings.Split(name, "/")
- if elems[len(elems)-1] == templateVar {
- isPattern = true
- elems = elems[:len(elems)-1]
- }
- }
-
- n, err := mt.findNode(nil, 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.explicitAccessLists = true
- }
- }
- n.parent.Unlock()
- n.Unlock()
- }
- return nil
-}
-
// Lookup implements rpc.Dispatcher.Lookup.
func (mt *mountTable) Lookup(name string) (interface{}, security.Authorizer, error) {
vlog.VI(2).Infof("*********************Lookup %s", name)
@@ -220,10 +180,10 @@
return m.servers.removeExpired() > 0
}
-// satisfies returns no error if the ctx + n.acls satisfies the associated one of the required Tags.
+// satisfies returns no error if the ctx + n.vPerms satisfies the associated one of the required Tags.
func (n *node) satisfies(mt *mountTable, ctx *context.T, call security.Call, tags []mounttable.Tag) error {
// No AccessLists means everything (for now).
- if ctx == nil || call == nil || tags == nil || n.acls == nil {
+ if ctx == nil || call == nil || tags == nil || n.vPerms == nil {
return nil
}
// "Self-RPCs" are always authorized.
@@ -233,7 +193,7 @@
// Match client's blessings against the AccessLists.
blessings, invalidB := security.RemoteBlessingNames(ctx, call)
for _, tag := range tags {
- if acl, exists := n.acls.GetPermissionsForTag(string(tag)); exists && acl.Includes(blessings...) {
+ if al, exists := n.vPerms.AccessListForTag(string(tag)); exists && al.Includes(blessings...) {
return nil
}
}
@@ -246,27 +206,27 @@
return verror.New(verror.ErrNoAccess, ctx, blessings)
}
-func expand(acl *access.AccessList, name string) *access.AccessList {
+func expand(al *access.AccessList, name string) *access.AccessList {
newAccessList := new(access.AccessList)
- for _, bp := range acl.In {
+ for _, bp := range al.In {
newAccessList.In = append(newAccessList.In, security.BlessingPattern(strings.Replace(string(bp), templateVar, name, -1)))
}
- for _, bp := range acl.NotIn {
+ for _, bp := range al.NotIn {
newAccessList.NotIn = append(newAccessList.NotIn, strings.Replace(bp, templateVar, name, -1))
}
return newAccessList
}
-// satisfiesTemplate returns no error if the ctx + n.amTemplate satisfies the associated one of
+// satisfiesTemplate returns no error if the ctx + n.permsTemplate satisfies the associated one of
// the required Tags.
func (n *node) satisfiesTemplate(ctx *context.T, call security.Call, tags []mounttable.Tag, name string) error {
- if n.amTemplate == nil {
+ if n.permsTemplate == nil {
return nil
}
// Match client's blessings against the AccessLists.
blessings, invalidB := security.RemoteBlessingNames(ctx, call)
for _, tag := range tags {
- if acl, exists := n.amTemplate[string(tag)]; exists && expand(&acl, name).Includes(blessings...) {
+ if al, exists := n.permsTemplate[string(tag)]; exists && expand(&al, name).Includes(blessings...) {
return nil
}
}
@@ -275,28 +235,32 @@
// copyAccessLists copies one nodes AccessLists to another and adds the clients blessings as
// patterns to the Admin tag.
-func copyAccessLists(ctx *context.T, call security.Call, cur *node) *TAMG {
+func copyAccessLists(ctx *context.T, call security.Call, cur *node) *VersionedPermissions {
if ctx == nil {
return nil
}
- if cur.acls == nil {
+ if call == nil {
return nil
}
- acls := cur.acls.Copy()
+ if cur.vPerms == nil {
+ return nil
+ }
+ vPerms := cur.vPerms.Copy()
blessings, _ := security.RemoteBlessingNames(ctx, call)
for _, b := range blessings {
- acls.Add(security.BlessingPattern(b), string(mounttable.Admin))
+ vPerms.Add(security.BlessingPattern(b), string(mounttable.Admin))
}
- return acls
+ vPerms.P.Normalize()
+ return vPerms
}
-// createTAMGFromTemplate creates a new TAMG from the template subsituting name for %% everywhere.
-func createTAMGFromTemplate(tam access.Permissions, name string) *TAMG {
- tamg := NewTAMG()
- for tag, acl := range tam {
- tamg.tam[tag] = *expand(&acl, name)
+// createVersionedPermissionsFromTemplate creates a new VersionedPermissions from the template subsituting name for %% everywhere.
+func createVersionedPermissionsFromTemplate(perms access.Permissions, name string) *VersionedPermissions {
+ vPerms := NewVersionedPermissions()
+ for tag, al := range perms {
+ vPerms.P[tag] = *expand(&al, name)
}
- return tamg
+ return vPerms
}
// traverse returns the node for the path represented by elems. If none exists and create is false, return nil.
@@ -350,10 +314,10 @@
// At this point cur is still locked, OK to use and change it.
next := mt.newNode()
next.parent = cur
- if cur.amTemplate != nil {
- next.acls = createTAMGFromTemplate(cur.amTemplate, e)
+ if cur.permsTemplate != nil {
+ next.vPerms = createVersionedPermissionsFromTemplate(cur.permsTemplate, e)
} else {
- next.acls = copyAccessLists(ctx, call, cur)
+ next.vPerms = copyAccessLists(ctx, call, cur)
}
if cur.children == nil {
cur.children = make(map[string]*node)
@@ -415,7 +379,7 @@
n.Unlock()
// If we removed the node, see if we can remove any of its
// ascendants.
- if removed {
+ if removed && len(elems) > 0 {
mt.removeUselessRecursive(elems[:len(elems)-1])
}
return nil, nil, nil
@@ -546,7 +510,7 @@
//
// We assume both n and n.parent are locked.
func (n *node) removeUseless(mt *mountTable) bool {
- if len(n.children) > 0 || n.mount.isActive() || n.explicitAccessLists {
+ if len(n.children) > 0 || n.mount.isActive() || n.explicitPermissions {
return false
}
for k, c := range n.parent.children {
@@ -631,6 +595,9 @@
return verror.New(errNotEmpty, ctx, ms.name)
}
mt.deleteNode(n.parent, ms.elems[len(ms.elems)-1])
+ if mt.persisting {
+ mt.persist.persistDelete(ms.name)
+ }
return nil
}
@@ -809,20 +776,29 @@
// If the caller is trying to add a Permission that they are no longer Admin in,
// retain the caller's blessings that were in Admin to prevent them from locking themselves out.
bnames, _ := security.RemoteBlessingNames(ctx, call.Security())
- if acl, ok := perms[string(mounttable.Admin)]; !ok || !acl.Includes(bnames...) {
- _, oldPerms := n.acls.Get()
- oldAcl := oldPerms[string(mounttable.Admin)]
- for _, bname := range bnames {
- if oldAcl.Includes(bname) {
+ if al, ok := perms[string(mounttable.Admin)]; !ok || !al.Includes(bnames...) {
+ _, oldPerms := n.vPerms.Get()
+ if oldPerms == nil {
+ for _, bname := range bnames {
perms.Add(security.BlessingPattern(bname), string(mounttable.Admin))
}
+ } else {
+ oldAl := oldPerms[string(mounttable.Admin)]
+ for _, bname := range bnames {
+ if oldAl.Includes(bname) {
+ perms.Add(security.BlessingPattern(bname), string(mounttable.Admin))
+ }
+ }
}
}
perms.Normalize()
- n.acls, err = n.acls.Set(version, perms)
+ n.vPerms, err = n.vPerms.Set(ctx, version, perms)
if err == nil {
- n.explicitAccessLists = true
+ if mt.persisting {
+ mt.persist.persistPerms(ms.name, n.vPerms)
+ }
+ n.explicitPermissions = true
}
return err
}
@@ -841,6 +817,6 @@
}
n.parent.Unlock()
defer n.Unlock()
- version, tam := n.acls.Get()
- return tam, version, nil
+ version, perms := n.vPerms.Get()
+ return perms, version, nil
}
diff --git a/services/mounttable/mounttablelib/mounttable_test.go b/services/mounttable/mounttablelib/mounttable_test.go
index 6d6175c..6dbc804 100644
--- a/services/mounttable/mounttablelib/mounttable_test.go
+++ b/services/mounttable/mounttablelib/mounttable_test.go
@@ -61,10 +61,10 @@
}
}
-func doGetPermissions(t *testing.T, ctx *context.T, ep, suffix string, shouldSucceed bool) (acl access.Permissions, version string) {
+func doGetPermissions(t *testing.T, ctx *context.T, ep, suffix string, shouldSucceed bool) (perms access.Permissions, version string) {
name := naming.JoinAddressName(ep, suffix)
client := v23.GetClient(ctx)
- if err := client.Call(ctx, name, "GetPermissions", nil, []interface{}{&acl, &version}, options.NoResolve{}); err != nil {
+ if err := client.Call(ctx, name, "GetPermissions", nil, []interface{}{&perms, &version}, options.NoResolve{}); err != nil {
if !shouldSucceed {
return
}
@@ -73,10 +73,10 @@
return
}
-func doSetPermissions(t *testing.T, ctx *context.T, ep, suffix string, acl access.Permissions, version string, shouldSucceed bool) {
+func doSetPermissions(t *testing.T, ctx *context.T, ep, suffix string, perms access.Permissions, version string, shouldSucceed bool) {
name := naming.JoinAddressName(ep, suffix)
client := v23.GetClient(ctx)
- if err := client.Call(ctx, name, "SetPermissions", []interface{}{acl, version}, nil, options.NoResolve{}); err != nil {
+ if err := client.Call(ctx, name, "SetPermissions", []interface{}{perms, version}, nil, options.NoResolve{}); err != nil {
if !shouldSucceed {
return
}
@@ -177,7 +177,7 @@
}
}
-func newMT(t *testing.T, acl string, rootCtx *context.T) (rpc.Server, string) {
+func newMT(t *testing.T, permsFile, persistDir string, rootCtx *context.T) (rpc.Server, string) {
reservedDisp := debuglib.NewDispatcher(vlog.Log.LogDir, nil)
ctx := v23.WithReservedNameDispatcher(rootCtx, reservedDisp)
server, err := v23.NewServer(ctx, options.ServesMountTable(true))
@@ -185,7 +185,7 @@
boom(t, "r.NewServer: %s", err)
}
// Add mount table service.
- mt, err := NewMountTableDispatcher(acl, "mounttable")
+ mt, err := NewMountTableDispatcher(permsFile, persistDir, "mounttable")
if err != nil {
boom(t, "NewMountTableDispatcher: %v", err)
}
@@ -202,7 +202,7 @@
return server, estr
}
-func newCollection(t *testing.T, acl string, rootCtx *context.T) (rpc.Server, string) {
+func newCollection(t *testing.T, rootCtx *context.T) (rpc.Server, string) {
server, err := v23.NewServer(rootCtx)
if err != nil {
boom(t, "r.NewServer: %s", err)
@@ -227,9 +227,9 @@
rootCtx, aliceCtx, bobCtx, shutdown := initTest()
defer shutdown()
- mt, mtAddr := newMT(t, "testdata/test.acl", rootCtx)
+ mt, mtAddr := newMT(t, "testdata/test.perms", "", rootCtx)
defer mt.Stop()
- collection, collectionAddr := newCollection(t, "testdata/test.acl", rootCtx)
+ collection, collectionAddr := newCollection(t, rootCtx)
defer collection.Stop()
collectionName := naming.JoinAddressName(collectionAddr, "collection")
@@ -266,36 +266,36 @@
checkContents(t, bobCtx, naming.JoinAddressName(mtAddr, "a/b/falls"), "falls mainly on the plain", false)
// Test getting/setting AccessLists.
- acl, version := doGetPermissions(t, rootCtx, mtAddr, "stuff", true)
- doSetPermissions(t, rootCtx, mtAddr, "stuff", acl, "xyzzy", false) // bad version
- doSetPermissions(t, rootCtx, mtAddr, "stuff", acl, version, true) // correct version
+ perms, version := doGetPermissions(t, rootCtx, mtAddr, "stuff", true)
+ doSetPermissions(t, rootCtx, mtAddr, "stuff", perms, "xyzzy", false) // bad version
+ doSetPermissions(t, rootCtx, mtAddr, "stuff", perms, version, true) // correct version
_, nversion := doGetPermissions(t, rootCtx, mtAddr, "stuff", true)
if nversion == version {
boom(t, "version didn't change after SetPermissions: %s", nversion)
}
- doSetPermissions(t, rootCtx, mtAddr, "stuff", acl, "", true) // no version
+ doSetPermissions(t, rootCtx, mtAddr, "stuff", perms, "", true) // no version
// Bob should be able to create nodes under the mounttable root but not alice.
- doSetPermissions(t, aliceCtx, mtAddr, "onlybob", acl, "", false)
- doSetPermissions(t, bobCtx, mtAddr, "onlybob", acl, "", true)
+ doSetPermissions(t, aliceCtx, mtAddr, "onlybob", perms, "", false)
+ doSetPermissions(t, bobCtx, mtAddr, "onlybob", perms, "", true)
- // Test that setting Permissions to an acl that don't include the the setter's
+ // Test that setting Permissions to permissions that don't include the the setter's
// blessings in Admin, automatically add their Blessings to Admin to prevent
// locking everyone out.
- acl, _ = doGetPermissions(t, bobCtx, mtAddr, "onlybob", true)
- noRootAcl := acl.Copy()
- noRootAcl.Clear("bob", "Admin")
- doSetPermissions(t, bobCtx, mtAddr, "onlybob", noRootAcl, "", true)
+ perms, _ = doGetPermissions(t, bobCtx, mtAddr, "onlybob", true)
+ noRootPerms := perms.Copy()
+ noRootPerms.Clear("bob", "Admin")
+ doSetPermissions(t, bobCtx, mtAddr, "onlybob", noRootPerms, "", true)
// This should succeed, because "bob" should automatically be added to "Admin"
// even though he cleared himself from "Admin".
- doSetPermissions(t, bobCtx, mtAddr, "onlybob", acl, "", true)
- // Test that adding a non-standard acl is normalized when retrieved.
- admin := acl["Admin"]
+ doSetPermissions(t, bobCtx, mtAddr, "onlybob", perms, "", true)
+ // Test that adding a non-standard perms is normalized when retrieved.
+ admin := perms["Admin"]
admin.In = []security.BlessingPattern{"bob", "bob"}
- acl["Admin"] = admin
- doSetPermissions(t, bobCtx, mtAddr, "onlybob", acl, "", true)
- acl, _ = doGetPermissions(t, bobCtx, mtAddr, "onlybob", true)
- if got, want := acl["Admin"].In, []security.BlessingPattern{"bob"}; !reflect.DeepEqual(got, want) {
+ perms["Admin"] = admin
+ doSetPermissions(t, bobCtx, mtAddr, "onlybob", perms, "", true)
+ perms, _ = doGetPermissions(t, bobCtx, mtAddr, "onlybob", true)
+ if got, want := perms["Admin"].In, []security.BlessingPattern{"bob"}; !reflect.DeepEqual(got, want) {
boom(t, "got %v, want %v", got, want)
}
@@ -397,7 +397,7 @@
rootCtx, shutdown := test.InitForTest()
defer shutdown()
- server, estr := newMT(t, "", rootCtx)
+ server, estr := newMT(t, "", "", rootCtx)
defer server.Stop()
// set up a mount space
@@ -444,7 +444,7 @@
rootCtx, aliceCtx, bobCtx, shutdown := initTest()
defer shutdown()
- server, estr := newMT(t, "testdata/test.acl", rootCtx)
+ server, estr := newMT(t, "testdata/test.perms", "", rootCtx)
defer server.Stop()
fakeServer := naming.JoinAddressName(estr, "quux")
@@ -463,7 +463,7 @@
rootCtx, aliceCtx, bobCtx, shutdown := initTest()
defer shutdown()
- server, estr := newMT(t, "testdata/test.acl", rootCtx)
+ server, estr := newMT(t, "testdata/test.perms", "", rootCtx)
defer server.Stop()
// set up a mount space
@@ -496,7 +496,7 @@
rootCtx, shutdown := test.InitForTest()
defer shutdown()
- server, estr := newMT(t, "", rootCtx)
+ server, estr := newMT(t, "", "", rootCtx)
defer server.Stop()
// Set up one mount.
@@ -511,8 +511,8 @@
// Set up a mount, then set the AccessList.
doMount(t, rootCtx, estr, "one/bright/day", fakeServer, true)
checkMatch(t, []string{"one", "one/bright", "one/bright/day"}, doGlob(t, rootCtx, estr, "", "*/..."))
- acl := access.Permissions{"Read": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}}}
- doSetPermissions(t, rootCtx, estr, "one/bright", acl, "", true)
+ perms := access.Permissions{"Read": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}}}
+ doSetPermissions(t, rootCtx, estr, "one/bright", perms, "", true)
// After the unmount we should still have everything above the AccessList.
doUnmount(t, rootCtx, estr, "one/bright/day", "", true)
@@ -523,7 +523,7 @@
rootCtx, aliceCtx, bobCtx, shutdown := initTest()
defer shutdown()
- server, estr := newMT(t, "testdata/test.acl", rootCtx)
+ server, estr := newMT(t, "testdata/test.perms", "", rootCtx)
defer server.Stop()
// set up a mount space
@@ -550,7 +550,7 @@
rootCtx, shutdown := test.InitForTest()
defer shutdown()
- server, estr := newMT(t, "", rootCtx)
+ server, estr := newMT(t, "", "", rootCtx)
defer server.Stop()
doMount(t, rootCtx, estr, "endpoint", naming.JoinAddressName(estr, "life/on/the/mississippi"), true)
@@ -564,9 +564,9 @@
rootCtx, shutdown := test.InitForTest()
defer shutdown()
- server, estr := newMT(t, "", rootCtx)
+ server, estr := newMT(t, "", "", rootCtx)
defer server.Stop()
- collection, collectionAddr := newCollection(t, "testdata/test.acl", rootCtx)
+ collection, collectionAddr := newCollection(t, rootCtx)
defer collection.Stop()
collectionName := naming.JoinAddressName(collectionAddr, "collection")
@@ -591,13 +591,13 @@
}
func TestBadAccessLists(t *testing.T) {
- _, err := NewMountTableDispatcher("testdata/invalid.acl", "mounttable")
+ _, err := NewMountTableDispatcher("testdata/invalid.perms", "", "mounttable")
if err == nil {
- boom(t, "Expected json parse error in acl file")
+ boom(t, "Expected json parse error in permissions file")
}
- _, err = NewMountTableDispatcher("testdata/doesntexist.acl", "mounttable")
+ _, err = NewMountTableDispatcher("testdata/doesntexist.perms", "", "mounttable")
if err != nil {
- boom(t, "Missing acl file should not cause an error")
+ boom(t, "Missing permissions file should not cause an error")
}
}
@@ -629,7 +629,7 @@
rootCtx, shutdown := test.InitForTest()
defer shutdown()
- server, estr := newMT(t, "", rootCtx)
+ server, estr := newMT(t, "", "", rootCtx)
defer server.Stop()
// Test flat tree
diff --git a/services/mounttable/mounttablelib/neighborhood.go b/services/mounttable/mounttablelib/neighborhood.go
index bfbb37d..b2a2a71 100644
--- a/services/mounttable/mounttablelib/neighborhood.go
+++ b/services/mounttable/mounttablelib/neighborhood.go
@@ -285,10 +285,10 @@
}
}
-func (*neighborhoodService) SetPermissions(ctx *context.T, _ rpc.ServerCall, acl access.Permissions, version string) error {
+func (*neighborhoodService) SetPermissions(ctx *context.T, _ rpc.ServerCall, _ access.Permissions, _ string) error {
return verror.New(errDoesntImplementSetPermissions, ctx)
}
-func (*neighborhoodService) GetPermissions(*context.T, rpc.ServerCall) (acl access.Permissions, version string, err error) {
+func (*neighborhoodService) GetPermissions(*context.T, rpc.ServerCall) (access.Permissions, string, error) {
return nil, "", nil
}
diff --git a/services/mounttable/mounttablelib/persist_test.go b/services/mounttable/mounttablelib/persist_test.go
new file mode 100644
index 0000000..a6b936b
--- /dev/null
+++ b/services/mounttable/mounttablelib/persist_test.go
@@ -0,0 +1,78 @@
+// 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 mounttablelib
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "reflect"
+ "testing"
+
+ "v.io/v23/security"
+ "v.io/v23/security/access"
+)
+
+func TestPersistence(t *testing.T) {
+ rootCtx, _, _, shutdown := initTest()
+ defer shutdown()
+
+ td, err := ioutil.TempDir("", "upyournose")
+ if err != nil {
+ t.Fatalf("Failed to make temporary dir: %s", err)
+ }
+ defer os.RemoveAll(td)
+ fmt.Printf("temp persist dir %s\n", td)
+ mt, mtAddr := newMT(t, "", td, rootCtx)
+
+ perms1 := access.Permissions{
+ "Read": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+ "Resolve": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+ "Create": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+ }
+ perms2 := access.Permissions{
+ "Read": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+ "Resolve": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+ "Create": access.AccessList{In: []security.BlessingPattern{"root"}},
+ }
+ perms3 := access.Permissions{
+ "Read": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+ "Resolve": access.AccessList{In: []security.BlessingPattern{security.AllPrincipals}},
+ "Create": access.AccessList{In: []security.BlessingPattern{"bob"}},
+ }
+
+ // Set some permissions. It should be persisted.
+ doSetPermissions(t, rootCtx, mtAddr, "a", perms1, "", true)
+ doSetPermissions(t, rootCtx, mtAddr, "a/b", perms2, "", true)
+ doSetPermissions(t, rootCtx, mtAddr, "a/b/c/d", perms2, "", true)
+ doSetPermissions(t, rootCtx, mtAddr, "a/b/c/d/e", perms2, "", true)
+ doDeleteSubtree(t, rootCtx, mtAddr, "a/b/c", true)
+ doSetPermissions(t, rootCtx, mtAddr, "a/c/d", perms3, "", true)
+ mt.Stop()
+
+ // Restart with the persisted data.
+ mt, mtAddr = newMT(t, "", td, rootCtx)
+
+ // Add root as Admin to each of the perms since the mounttable itself will.
+ perms1["Admin"] = access.AccessList{In: []security.BlessingPattern{"root"}}
+ perms2["Admin"] = access.AccessList{In: []security.BlessingPattern{"root"}}
+ perms3["Admin"] = access.AccessList{In: []security.BlessingPattern{"root"}}
+
+ // Check deletion.
+ checkExists(t, rootCtx, mtAddr, "a/b", true)
+ checkExists(t, rootCtx, mtAddr, "a/b/c", false)
+
+ // Check Persistance.
+ if perm, _ := doGetPermissions(t, rootCtx, mtAddr, "a", true); !reflect.DeepEqual(perm, perms1) {
+ t.Fatalf("a: got %v, want %v", perm, perms1)
+ }
+ if perm, _ := doGetPermissions(t, rootCtx, mtAddr, "a/b", true); !reflect.DeepEqual(perm, perms2) {
+ t.Fatalf("a/b: got %v, want %v", perm, perms2)
+ }
+ if perm, _ := doGetPermissions(t, rootCtx, mtAddr, "a/c/d", true); !reflect.DeepEqual(perm, perms3) {
+ t.Fatalf("a/c/d: got %v, want %v", perm, perms3)
+ }
+ mt.Stop()
+}
diff --git a/services/mounttable/mounttablelib/persistentstore.go b/services/mounttable/mounttablelib/persistentstore.go
new file mode 100644
index 0000000..1dfd2f8
--- /dev/null
+++ b/services/mounttable/mounttablelib/persistentstore.go
@@ -0,0 +1,180 @@
+// 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 mounttablelib
+
+import (
+ "encoding/json"
+ "io"
+ "os"
+ "path"
+ "strings"
+ "sync"
+
+ "v.io/x/lib/vlog"
+)
+
+type store struct {
+ l sync.Mutex
+ mt *mountTable
+ dir string
+ enc *json.Encoder
+ f *os.File
+}
+
+type storeElement struct {
+ N string // Name of affected node
+ V VersionedPermissions
+ D bool // True if the subtree at N has been deleted
+}
+
+// newPersistentStore will read the permissions log from the directory and apply them to the
+// in memory tree. It will then write a new file from the in memory tree and any new permission
+// changes will be appened to this file. By writing into a new file, we effectively compress
+// the permissions file since any set permissions that have been deleted or overwritten will be
+// lost.
+//
+// The code manages three files in the directory 'dir':
+// persistent.permslog - the log of permissions. A new log entry is added with each SetPermissions or
+// Delete RPC.
+// tmp.permslog - a temporary file created whenever we restart. Once we write the current state into it,
+// it will be renamed persistent.perms becoming the new log.
+// old.permslog - the previous version of persistent.perms. This is left around primarily for debugging
+// and as an emergency backup.
+func newPersistentStore(mt *mountTable, dir string) persistence {
+ s := &store{mt: mt, dir: dir}
+ file := path.Join(dir, "persistent.permslog")
+ tmp := path.Join(dir, "tmp.permslog")
+ old := path.Join(dir, "old.permslog")
+
+ // If the permissions file doesn't exist, try renaming the temporary one.
+ f, err := os.Open(file)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ vlog.Fatalf("cannot open %s: %s", file, err)
+ }
+ os.Rename(tmp, file)
+ if f, err = os.Open(file); err != nil && !os.IsNotExist(err) {
+ vlog.Fatalf("cannot open %s: %s", file, err)
+ }
+ } else {
+ os.Remove(tmp)
+ }
+
+ // Parse the permissions file and apply it to the in memory tree.
+ if f != nil {
+ if err := s.parseLogFile(f); err != nil {
+ f.Close()
+ // Log the error but keep going. There's not much else we can do.
+ vlog.Infof("parsing old persistent permissions file %s: %s", file, err)
+ }
+ f.Close()
+ }
+
+ // Write the permissions to a temporary file. This compresses
+ // the file since it writes out only the end state.
+ f, err = os.OpenFile(tmp, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
+ if err != nil {
+ // Log the error but keep going, don't compress, just append to the current file.
+ vlog.Infof("can't rewrite persistent permissions file %s: %s", file, err)
+ if f, err = os.OpenFile(file, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600); err != nil {
+ vlog.Fatalf("can't append to log %s: %s", file, err)
+ }
+ f.Seek(0, 2)
+ s.enc = json.NewEncoder(f)
+ return s
+ }
+ s.enc = json.NewEncoder(f)
+ s.depthFirstPersist(mt.root, "")
+ f.Close()
+
+ // Switch names and remove the old file.
+ if err := os.Remove(old); err != nil {
+ vlog.Infof("removing %s: %s", old, err)
+ }
+ if err := os.Rename(file, old); err != nil {
+ vlog.Infof("renaming %s to %s: %s", file, old, err)
+ }
+ if err := os.Rename(tmp, file); err != nil {
+ vlog.Fatalf("renaming %s to %s: %s", tmp, file, err)
+ }
+
+ // Reopen the new log file. We could have just kept around the encoder used
+ // to create it but that assumes that, after the Rename above, the s.f still
+ // points to the same file. Only true on Unix like file systems.
+ f, err = os.OpenFile(file, os.O_WRONLY|os.O_APPEND, 0600)
+ if err != nil {
+ vlog.Fatalf("can't open %s: %s", file, err)
+ }
+ f.Seek(0, 2)
+ s.enc = json.NewEncoder(f)
+ return s
+}
+
+// parseLogFile reads a file and parses the contained VersionedPermissions .
+func (s *store) parseLogFile(f *os.File) error {
+ if f == nil {
+ return nil
+ }
+ vlog.VI(2).Infof("parseLogFile(%s)", f.Name())
+ mt := s.mt
+ decoder := json.NewDecoder(f)
+ for {
+ var e storeElement
+ if err := decoder.Decode(&e); err != nil {
+ if err == io.EOF {
+ break
+ }
+ return err
+ }
+
+ elems := strings.Split(e.N, "/")
+ n, err := mt.findNode(nil, nil, elems, true, nil)
+ if n != nil || err == nil {
+ if e.D {
+ mt.deleteNode(n.parent, elems[len(elems)-1])
+ vlog.VI(2).Infof("deleted %s", e.N)
+ } else {
+ n.vPerms = &e.V
+ n.explicitPermissions = true
+ vlog.VI(2).Infof("added versions permissions %v to %s", e.V, e.N)
+ }
+ }
+ n.parent.Unlock()
+ n.Unlock()
+ }
+ return nil
+}
+
+// depthFirstPersist performs a recursive depth first traversal logging any explicit permissions.
+// Doing this immediately after reading in a log file effectively compresses the log file since
+// any duplicate or deleted entries disappear.
+func (s *store) depthFirstPersist(n *node, name string) {
+ if n.explicitPermissions {
+ s.persistPerms(name, n.vPerms)
+ }
+ for nodeName, c := range n.children {
+ s.depthFirstPersist(c, path.Join(name, nodeName))
+ }
+}
+
+// persistPerms appends a changed permission to the log.
+func (s *store) persistPerms(name string, vPerms *VersionedPermissions) error {
+ s.l.Lock()
+ defer s.l.Unlock()
+ e := storeElement{N: name, V: *vPerms}
+ return s.enc.Encode(&e)
+}
+
+// persistDelete appends a single deletion to the log.
+func (s *store) persistDelete(name string) error {
+ s.l.Lock()
+ defer s.l.Unlock()
+ e := storeElement{N: name, D: true}
+ return s.enc.Encode(&e)
+}
+
+func (s *store) close() {
+ s.f.Close()
+}
diff --git a/services/mounttable/mounttablelib/servers.go b/services/mounttable/mounttablelib/servers.go
index d63bfea..0396ecc 100644
--- a/services/mounttable/mounttablelib/servers.go
+++ b/services/mounttable/mounttablelib/servers.go
@@ -15,7 +15,7 @@
"v.io/x/lib/vlog"
)
-func StartServers(ctx *context.T, listenSpec rpc.ListenSpec, mountName, nhName, aclFile, debugPrefix string) (string, func(), error) {
+func StartServers(ctx *context.T, listenSpec rpc.ListenSpec, mountName, nhName, permsFile, persistDir, debugPrefix string) (string, func(), error) {
var stopFuncs []func() error
stop := func() {
for i := len(stopFuncs) - 1; i >= 0; i-- {
@@ -29,7 +29,7 @@
return "", nil, err
}
stopFuncs = append(stopFuncs, mtServer.Stop)
- mt, err := NewMountTableDispatcher(aclFile, debugPrefix)
+ mt, err := NewMountTableDispatcher(permsFile, persistDir, debugPrefix)
if err != nil {
vlog.Errorf("NewMountTable failed: %v", err)
stop()
diff --git a/services/mounttable/mounttablelib/tamg.go b/services/mounttable/mounttablelib/tamg.go
deleted file mode 100644
index 26a8597..0000000
--- a/services/mounttable/mounttablelib/tamg.go
+++ /dev/null
@@ -1,74 +0,0 @@
-// 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 mounttablelib
-
-import (
- "strconv"
-
- "v.io/v23/security"
- "v.io/v23/security/access"
- "v.io/v23/verror"
-)
-
-// TAMG associates a generation with a Permissions
-type TAMG struct {
- tam access.Permissions
- generation int32
-}
-
-func NewTAMG() *TAMG {
- return &TAMG{tam: make(access.Permissions)}
-}
-
-// Set sets the AccessLists iff generation matches the current generation. If the set happens, the generation is advanced.
-// If b is nil, this creates a new TAMG.
-func (b *TAMG) Set(genstr string, tam access.Permissions) (*TAMG, error) {
- if b == nil {
- b = new(TAMG)
- }
- if len(genstr) > 0 {
- gen, err := strconv.ParseInt(genstr, 10, 32)
- if err != nil {
- return b, verror.NewErrBadVersion(nil)
- }
- if gen >= 0 && int32(gen) != b.generation {
- return b, verror.NewErrBadVersion(nil)
- }
- }
- b.tam = tam
- b.generation++
- // Protect against wrap.
- if b.generation < 0 {
- b.generation = 0
- }
- return b, nil
-}
-
-// Get returns the current generation and acls.
-func (b *TAMG) Get() (string, access.Permissions) {
- if b == nil {
- return "", nil
- }
- return strconv.FormatInt(int64(b.generation), 10), b.tam
-}
-
-// GetPermissionsForTag returns the current acls for the given tag.
-func (b *TAMG) GetPermissionsForTag(tag string) (access.AccessList, bool) {
- acl, exists := b.tam[tag]
- return acl, exists
-}
-
-// Copy copies the receiver.
-func (b *TAMG) Copy() *TAMG {
- nt := new(TAMG)
- nt.tam = b.tam.Copy()
- nt.generation = b.generation
- return nt
-}
-
-// Add adds the blessing pattern to the tag in the reciever.
-func (b *TAMG) Add(pattern security.BlessingPattern, tag string) {
- b.tam.Add(pattern, tag)
-}
diff --git a/services/mounttable/mounttablelib/testdata/invalid.acl b/services/mounttable/mounttablelib/testdata/invalid.perms
similarity index 100%
rename from services/mounttable/mounttablelib/testdata/invalid.acl
rename to services/mounttable/mounttablelib/testdata/invalid.perms
diff --git a/services/mounttable/mounttablelib/testdata/noRoot.acl b/services/mounttable/mounttablelib/testdata/noRoot.perms
similarity index 100%
rename from services/mounttable/mounttablelib/testdata/noRoot.acl
rename to services/mounttable/mounttablelib/testdata/noRoot.perms
diff --git a/services/mounttable/mounttablelib/testdata/test.acl b/services/mounttable/mounttablelib/testdata/test.perms
similarity index 100%
rename from services/mounttable/mounttablelib/testdata/test.acl
rename to services/mounttable/mounttablelib/testdata/test.perms
diff --git a/services/mounttable/mounttablelib/versionedpermissions.go b/services/mounttable/mounttablelib/versionedpermissions.go
new file mode 100644
index 0000000..f68401f
--- /dev/null
+++ b/services/mounttable/mounttablelib/versionedpermissions.go
@@ -0,0 +1,145 @@
+// 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 mounttablelib
+
+import (
+ "encoding/json"
+ "io"
+ "os"
+ "strconv"
+ "strings"
+
+ "v.io/v23/context"
+ "v.io/v23/security"
+ "v.io/v23/security/access"
+ "v.io/v23/services/mounttable"
+ "v.io/v23/verror"
+ "v.io/x/lib/vlog"
+)
+
+// VersionedPermissions associates a Version with a Permissions
+type VersionedPermissions struct {
+ V int32
+ P access.Permissions
+}
+
+func NewVersionedPermissions() *VersionedPermissions {
+ return &VersionedPermissions{P: make(access.Permissions)}
+}
+
+// Set sets the Permissions iff Version matches the current Version. If the set happens, the Version is advanced.
+// If b is nil, this creates a new VersionedPermissions.
+func (b *VersionedPermissions) Set(ctx *context.T, verstr string, perm access.Permissions) (*VersionedPermissions, error) {
+ if b == nil {
+ b = new(VersionedPermissions)
+ }
+ if len(verstr) > 0 {
+ gen, err := strconv.ParseInt(verstr, 10, 32)
+ if err != nil {
+ return b, verror.NewErrBadVersion(ctx)
+ }
+ if gen >= 0 && int32(gen) != b.V {
+ return b, verror.NewErrBadVersion(ctx)
+ }
+ }
+ b.P = perm
+ b.V++
+ // Protect against wrap.
+ if b.V < 0 {
+ b.V = 0
+ }
+ return b, nil
+}
+
+// Get returns the current Version and Permissions.
+func (b *VersionedPermissions) Get() (string, access.Permissions) {
+ if b == nil {
+ return "", nil
+ }
+ return strconv.FormatInt(int64(b.V), 10), b.P
+}
+
+// AccessListForTag returns the current access list for the given tag.
+func (b *VersionedPermissions) AccessListForTag(tag string) (access.AccessList, bool) {
+ al, exists := b.P[tag]
+ return al, exists
+}
+
+// Copy copies the receiver.
+func (b *VersionedPermissions) Copy() *VersionedPermissions {
+ nt := new(VersionedPermissions)
+ nt.P = b.P.Copy()
+ nt.V = b.V
+ return nt
+}
+
+// Add adds the blessing pattern to the tag in the reciever.
+func (b *VersionedPermissions) Add(pattern security.BlessingPattern, tag string) {
+ b.P.Add(pattern, tag)
+}
+
+// parsePermFile reads a file and parses the contained permissions.
+func (mt *mountTable) parsePermFile(path string) error {
+ vlog.VI(2).Infof("parsePermFile(%s)", path)
+ if path == "" {
+ return nil
+ }
+ // A map from node name to permissions.
+ var pm map[string]access.Permissions
+ f, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ decoder := json.NewDecoder(f)
+ for {
+ if err = decoder.Decode(&pm); err != nil {
+ if err == io.EOF {
+ break
+ }
+ return err
+ }
+ for name, perms := range pm {
+ var elems []string
+ isPattern := false
+
+ // The configuration file allows patterns and also will cause the superuser to
+ // be set to the root's administrator.
+ if len(name) == 0 {
+ // If the config file has is an Admin tag on the root AccessList, the
+ // list of Admin users is the equivalent of a super user for
+ // the whole table. Later SetPermissions do not update the set
+ // of super users.
+ if bp, exists := perms[string(mounttable.Admin)]; exists {
+ mt.superUsers = bp
+ }
+ } else {
+ // AccessList templates terminate with a %% element. These are very
+ // constrained matches, i.e., the trailing element of the name
+ // is copied into every %% in the AccessList.
+ elems = strings.Split(name, "/")
+ if elems[len(elems)-1] == templateVar {
+ isPattern = true
+ elems = elems[:len(elems)-1]
+ }
+ }
+
+ // Create name and add the Permissions map to it.
+ n, err := mt.findNode(nil, nil, elems, true, nil)
+ if n != nil || err == nil {
+ vlog.VI(2).Infof("added perms %v to %s", perms, name)
+ if isPattern {
+ n.permsTemplate = perms
+ } else {
+ n.vPerms, _ = n.vPerms.Set(nil, "", perms)
+ n.explicitPermissions = true
+ }
+ }
+ n.parent.Unlock()
+ n.Unlock()
+ }
+ }
+ return nil
+}
diff --git a/services/wspr/internal/app/app_test.go b/services/wspr/internal/app/app_test.go
index 11facfe..aca197c 100644
--- a/services/wspr/internal/app/app_test.go
+++ b/services/wspr/internal/app/app_test.go
@@ -122,7 +122,7 @@
}
func startMountTableServer(ctx *context.T) (rpc.Server, naming.Endpoint, error) {
- mt, err := mounttablelib.NewMountTableDispatcher("", "mounttable")
+ mt, err := mounttablelib.NewMountTableDispatcher("", "", "mounttable")
if err != nil {
return nil, nil, err
}
diff --git a/services/wspr/internal/browspr/browspr_test.go b/services/wspr/internal/browspr/browspr_test.go
index 4501cec..76b6185 100644
--- a/services/wspr/internal/browspr/browspr_test.go
+++ b/services/wspr/internal/browspr/browspr_test.go
@@ -31,7 +31,7 @@
//go:generate v23 test generate
func startMounttable(ctx *context.T) (rpc.Server, naming.Endpoint, error) {
- mt, err := mounttablelib.NewMountTableDispatcher("", "mounttable")
+ mt, err := mounttablelib.NewMountTableDispatcher("", "", "mounttable")
if err != nil {
return nil, nil, err
}