services/groups: Authorization fixes.
(1) NewManager was incorrectly using the default authorization policy
(by returning nil in Lookup), that is more restrictive and was
inconsistent with the intent specified in the comment. The result
of this was that no operations on the group server (i.e., any operation
on any group) was possible unless the client was a delegate of the
principal running the group server. Switched to security.AllowEveryone
to be consistent with the comment.
(2) Enable authorization checks on the creation operation - NewManager
is provided with an authorization policy for Create operations (via an
Authorizer implementation).
(3) Implement an experimental authorization policy for creates: Group
names must begin with the "user id" of the creator.
Change-Id: I3575b5b4d35dcdaa8da5e9b2997710ae1d618dac
diff --git a/services/groups/groupsd/main.go b/services/groups/groupsd/main.go
index 38d04bd..e1743db 100644
--- a/services/groups/groupsd/main.go
+++ b/services/groups/groupsd/main.go
@@ -11,14 +11,15 @@
import (
"fmt"
+ "strings"
"v.io/v23"
"v.io/v23/context"
+ "v.io/v23/conventions"
"v.io/v23/rpc"
"v.io/v23/security"
- "v.io/v23/security/access"
+ "v.io/v23/verror"
"v.io/x/lib/cmdline"
- "v.io/x/ref/lib/security/securityflag"
"v.io/x/ref/lib/signals"
"v.io/x/ref/lib/v23cmd"
_ "v.io/x/ref/runtime/factories/roaming"
@@ -32,6 +33,8 @@
flagEngine string
flagRootDir string
flagPersist string
+
+ errNotAuthorizedToCreate = verror.Register("v.io/x/ref/services/groups/groupsd.errNotAuthorizedToCreate", verror.NoRetry, "{1} {2} Creator user ids {3} are not authorized to create group {4}")
)
func main() {
@@ -43,16 +46,27 @@
cmdline.Main(cmdGroupsD)
}
-// defaultPerms returns a permissions object that grants all permissions to the
-// provided blessing patterns.
-func defaultPerms(blessingPatterns []security.BlessingPattern) access.Permissions {
- perms := access.Permissions{}
- for _, tag := range access.AllTypicalTags() {
- for _, bp := range blessingPatterns {
- perms.Add(bp, string(tag))
+// Authorizer implementing the authorization policy for Create operations.
+//
+// A user is allowed to create any group that begins with the user id.
+//
+// TODO(ashankar): This is experimental use of the "conventions" API and of a
+// creation policy. This policy was thought of in a 5 minute period. Think
+// about this more!
+type createAuthorizer struct{}
+
+func (createAuthorizer) Authorize(ctx *context.T, call security.Call) error {
+ userids := conventions.GetClientUserIds(ctx, call)
+ for _, uid := range userids {
+ if strings.HasPrefix(call.Suffix(), uid+"/") {
+ return nil
}
}
- return perms
+ // Revert to the default authorization policy.
+ if err := security.DefaultAuthorizer().Authorize(ctx, call); err == nil {
+ return nil
+ }
+ return verror.New(errNotAuthorizedToCreate, ctx, userids, call.Suffix())
}
var cmdGroupsD = &cmdline.Command{
@@ -66,16 +80,6 @@
}
func runGroupsD(ctx *context.T, env *cmdline.Env, args []string) error {
- perms, err := securityflag.PermissionsFromFlag()
- if err != nil {
- return fmt.Errorf("PermissionsFromFlag() failed: %v", err)
- }
- if perms != nil {
- ctx.Infof("Using permissions from command line flag.")
- } else {
- ctx.Infof("No permissions flag provided. Giving local principal all permissions.")
- perms = defaultPerms(security.DefaultBlessingPatterns(v23.GetPrincipal(ctx)))
- }
var dispatcher rpc.Dispatcher
switch flagEngine {
case "leveldb":
@@ -83,9 +87,9 @@
if err != nil {
ctx.Fatalf("Open(%v) failed: %v", flagRootDir, err)
}
- dispatcher = server.NewManager(store, perms)
+ dispatcher = server.NewManager(store, createAuthorizer{})
case "memstore":
- dispatcher = server.NewManager(mem.New(), perms)
+ dispatcher = server.NewManager(mem.New(), createAuthorizer{})
default:
return fmt.Errorf("unknown storage engine %v", flagEngine)
}
diff --git a/services/groups/internal/server/group.go b/services/groups/internal/server/group.go
index 715ba07..241345a 100644
--- a/services/groups/internal/server/group.go
+++ b/services/groups/internal/server/group.go
@@ -26,15 +26,11 @@
var _ groups.GroupServerMethods = (*group)(nil)
-// TODO(sadovsky): Reserve buckets for users.
+// TODO(sadovsky): Limit the number of groups that a particular user
+// (v23/conventsions.GetClientUserId) can create?
func (g *group) Create(ctx *context.T, call rpc.ServerCall, perms access.Permissions, entries []groups.BlessingPatternChunk) error {
- // Perform Permissions check.
- // TODO(sadovsky): Enable this Permissions check and acquire a lock on the
- // group server Permissions.
- if false {
- if err := g.authorize(ctx, call.Security(), g.m.perms); err != nil {
- return err
- }
+ if err := g.m.createAuthorizer.Authorize(ctx, call.Security()); err != nil {
+ return err
}
if perms == nil {
perms = access.Permissions{}
diff --git a/services/groups/internal/server/manager.go b/services/groups/internal/server/manager.go
index aa8595f..4e9a071 100644
--- a/services/groups/internal/server/manager.go
+++ b/services/groups/internal/server/manager.go
@@ -10,27 +10,28 @@
"v.io/v23/context"
"v.io/v23/rpc"
"v.io/v23/security"
- "v.io/v23/security/access"
"v.io/v23/services/groups"
"v.io/x/ref/services/groups/internal/store"
)
type manager struct {
- st store.Store
- perms access.Permissions
+ st store.Store
+ createAuthorizer security.Authorizer
}
-var _ rpc.Dispatcher = (*manager)(nil)
-
-func NewManager(st store.Store, perms access.Permissions) *manager {
- return &manager{st: st, perms: perms}
+// NewManager returns an rpc.Dispatcher implementation for a namespace of groups.
+//
+// The authorization policy for the creation of new groups will be controlled
+// by the provided Authorizer.
+func NewManager(st store.Store, auth security.Authorizer) rpc.Dispatcher {
+ return &manager{st: st, createAuthorizer: auth}
}
func (m *manager) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
suffix = strings.TrimPrefix(suffix, "/")
// TODO(sadovsky): Check that suffix is a valid group name.
- // TODO(sadovsky): Use a real authorizer. Note, this authorizer will be
- // relatively permissive. Stricter access control happens in the individual
- // RPC methods. See syncgroupserver/main.go for example.
- return groups.GroupServer(&group{name: suffix, m: m}), nil, nil
+ // A permissive authorizer (AllowEveryone) is used here since access
+ // control happens in the implementation of individual RPC methods. See
+ // the implementation of the group operations on the 'group' type.
+ return groups.GroupServer(&group{name: suffix, m: m}), security.AllowEveryone(), nil
}
diff --git a/services/groups/internal/server/server_test.go b/services/groups/internal/server/server_test.go
index b4393b0..acb13cc 100644
--- a/services/groups/internal/server/server_test.go
+++ b/services/groups/internal/server/server_test.go
@@ -5,10 +5,12 @@
package server_test
import (
+ "fmt"
"io/ioutil"
"os"
"reflect"
"runtime/debug"
+ "strings"
"testing"
"v.io/v23"
@@ -95,9 +97,19 @@
return reflect.DeepEqual(a, b)
}
+// security.Authorizer implementation that disallows any operations on a
+// "reserved*" suffix. Used to test that the provided authorization policy
+// is applied to group creation operations.
+type reservedAuthorizer struct{}
+
+func (reservedAuthorizer) Authorize(_ *context.T, call security.Call) error {
+ if strings.HasPrefix(call.Suffix(), "reserved") {
+ return fmt.Errorf("operations on %q are reserved and not authorized", call.Suffix())
+ }
+ return nil
+}
+
func newServer(ctx *context.T, be backend) (string, func()) {
- // TODO(sadovsky): Pass in perms and test perms-checking in Group.Create().
- perms := access.Permissions{}
var st store.Store
var path string
var err error
@@ -118,7 +130,7 @@
ctx.Fatal("unknown backend: ", be)
}
- m := server.NewManager(st, perms)
+ m := server.NewManager(st, reservedAuthorizer{})
ctx, server, err := v23.WithNewDispatchingServer(ctx, "", m)
if err != nil {
@@ -181,6 +193,11 @@
ctx, serverName, cleanup := setupOrDie(be)
defer cleanup()
+ // Unauthorized creates should fail.
+ // This fails because of the reservedAuthorizer used in setupOrDie.
+ if err := groups.GroupClient(naming.JoinAddressName(serverName, "reservedGroup")).Create(ctx, nil, nil); err == nil {
+ t.Errorf("Creation of reservedGroup succeeded")
+ }
// Create a group with a default perms and no entries.
g := groups.GroupClient(naming.JoinAddressName(serverName, "grpA"))
if err := g.Create(ctx, nil, nil); err != nil {