From 38998186c1bd5601c0be0a2f094973b4053eae80 Mon Sep 17 00:00:00 2001 From: Animesh Chandra Pathak Date: Tue, 24 Dec 2019 23:01:09 +0530 Subject: [PATCH] Add guardians group with full authorization (#4447) (cherry picked from commit 1ee5cfa61ffe567f3ca3c108ad282e76b423b5b5) --- edgraph/access_ee.go | 94 ++++++++++++++++++++++++++++---------------- ee/acl/acl.go | 13 +----- ee/acl/acl_test.go | 77 ++++++++++++++++++++++++++++++++++++ ee/acl/run_ee.go | 6 ++- ee/acl/utils.go | 20 +++++++++- x/x.go | 12 ++++++ 6 files changed, 173 insertions(+), 49 deletions(-) diff --git a/edgraph/access_ee.go b/edgraph/access_ee.go index a9b08349099..97ab966264b 100644 --- a/edgraph/access_ee.go +++ b/edgraph/access_ee.go @@ -358,60 +358,87 @@ func ResetAcl() { return } - upsertGroot := func(ctx context.Context) error { - queryVars := map[string]string{ - "$userid": x.GrootId, - "$password": "", - } - queryRequest := api.Request{ - Query: queryUser, - Vars: queryVars, + // guardians is the group of users who have complete access over all predicates. + upsertGuardians := func(ctx context.Context) error { + query := fmt.Sprintf(` + { + guid as var(func: eq(dgraph.xid, "%s")) + } + `, x.GuardiansId) + groupNQuads := acl.CreateGroupNQuads(x.GuardiansId) + req := &api.Request{ + CommitNow: true, + Query: query, + Mutations: []*api.Mutation{ + { + Set: groupNQuads, + Cond: "@if(eq(len(guid), 0))", + }, + }, } - queryResp, err := (&Server{}).doQuery(ctx, &queryRequest, NoAuthorize) - if err != nil { - return errors.Wrapf(err, "while querying user with id %s", x.GrootId) + if _, err := (&Server{}).doQuery(ctx, req, NoAuthorize); err != nil { + return errors.Wrapf(err, "while upserting group with id %s", x.GuardiansId) } - startTs := queryResp.GetTxn().StartTs - rootUser, err := acl.UnmarshalUser(queryResp, "user") - if err != nil { - return errors.Wrapf(err, "while unmarshaling the root user") - } - if rootUser != nil { - glog.Infof("The groot account already exists, no need to insert again") - return nil - } + glog.Infof("Successfully upserted the guardian group") + return nil + } - // Insert Groot. - createUserNQuads := acl.CreateUserNQuads(x.GrootId, "password") + // groot is the default user of guardians group. + upsertGroot := func(ctx context.Context) error { + query := fmt.Sprintf(` + { + grootid as var(func: eq(dgraph.xid, "%s")) + guid as var(func: eq(dgraph.xid, "%s")) + } + `, x.GrootId, x.GuardiansId) + userNQuads := acl.CreateUserNQuads(x.GrootId, "password") + userNQuads = append(userNQuads, &api.NQuad{ + Subject: "_:newuser", + Predicate: "dgraph.user.group", + ObjectId: "uid(guid)", + }) req := &api.Request{ - StartTs: startTs, CommitNow: true, + Query: query, Mutations: []*api.Mutation{ { - Set: createUserNQuads, + Set: userNQuads, + // Assuming that if groot exists, it is in guardian group + Cond: "@if(eq(len(grootid), 0) and gt(len(guid), 0))", }, }, } - _, err = (&Server{}).doQuery(context.Background(), req, NoAuthorize) - if err != nil { - return err + if _, err := (&Server{}).doQuery(ctx, req, NoAuthorize); err != nil { + return errors.Wrapf(err, "while upserting user with id %s", x.GrootId) } - glog.Infof("Successfully upserted the groot account") + + glog.Infof("Successfully upserted groot account") return nil } + for { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + if err := upsertGuardians(ctx); err != nil { + glog.Infof("Unable to upsert the guardian group. Error: %v", err) + time.Sleep(100 * time.Millisecond) + continue + } + break + } + for { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() if err := upsertGroot(ctx); err != nil { glog.Infof("Unable to upsert the groot account. Error: %v", err) time.Sleep(100 * time.Millisecond) - } else { - return + continue } + break } } @@ -504,7 +531,8 @@ func authorizeAlter(ctx context.Context, op *api.Operation) error { userId = userData[0] groupIds = userData[1:] - if userId == x.GrootId { + if x.IsGuardian(groupIds) { + // Members of guardian group are allowed to alter anything. return nil } } @@ -704,8 +732,8 @@ func authorizeQuery(ctx context.Context, parsedReq *gql.Result) error { userId = userData[0] groupIds = userData[1:] - if userId == x.GrootId { - // groot is allowed to query anything + if x.IsGuardian(groupIds) { + // Members of guardian groups are allowed to query anything. return nil } } diff --git a/ee/acl/acl.go b/ee/acl/acl.go index f69ba191cec..590a71b9d74 100644 --- a/ee/acl/acl.go +++ b/ee/acl/acl.go @@ -151,18 +151,7 @@ func groupAdd(conf *viper.Viper, groupId string) error { return errors.Errorf("group %q already exists", groupId) } - createGroupNQuads := []*api.NQuad{ - { - Subject: "_:newgroup", - Predicate: "dgraph.xid", - ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: groupId}}, - }, - { - Subject: "_:newgroup", - Predicate: "dgraph.type", - ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: "Group"}}, - }, - } + createGroupNQuads := CreateGroupNQuads(groupId) mu := &api.Mutation{ CommitNow: true, diff --git a/ee/acl/acl_test.go b/ee/acl/acl_test.go index d6bebe45734..b8b0111a815 100644 --- a/ee/acl/acl_test.go +++ b/ee/acl/acl_test.go @@ -474,3 +474,80 @@ func TestUnauthorizedDeletion(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "PermissionDenied") } + +func TestGuardianAccess(t *testing.T) { + ctx, _ := context.WithTimeout(context.Background(), 100*time.Second) + + dg, err := testutil.DgraphClientWithGroot(testutil.SockAddr) + require.NoError(t, err) + + testutil.DropAll(t, dg) + op := api.Operation{Schema: "unauthpred: string @index(exact) ."} + require.NoError(t, dg.Alter(ctx, &op)) + + err = addNewUserToGroup("guardian", "guardianpass", "guardians") + require.NoError(t, err, "Error while adding user to guardians group") + + mutation := &api.Mutation{ + SetNquads: []byte("_:a \"testdata\" ."), + CommitNow: true, + } + resp, err := dg.NewTxn().Mutate(ctx, mutation) + require.NoError(t, err) + + nodeUID, ok := resp.Uids["a"] + require.True(t, ok) + + time.Sleep(6 * time.Second) + gClient, err := testutil.DgraphClient(testutil.SockAddr) + require.NoError(t, err, "Error while creating client") + + gClient.Login(ctx, "guardian", "guardianpass") + + mutString := fmt.Sprintf("<%s> \"testdata\" .", nodeUID) + mutation = &api.Mutation{SetNquads: []byte(mutString), CommitNow: true} + _, err = gClient.NewTxn().Mutate(ctx, mutation) + require.NoError(t, err, "Error while mutating unauthorized predicate") + + query := ` + { + me(func: eq(unauthpred, "testdata")) { + uid + } + }` + + resp, err = gClient.NewTxn().Query(ctx, query) + require.NoError(t, err, "Error while querying unauthorized predicate") + require.Contains(t, string(resp.GetJson()), "uid") + + op = api.Operation{Schema: "unauthpred: int ."} + require.NoError(t, gClient.Alter(ctx, &op), "Error while altering unauthorized predicate") + + err = removeUserFromGroups("guardian") + require.NoError(t, err, "Error while removing guardian from guardians group") + + _, err = gClient.NewTxn().Query(ctx, query) + require.Error(t, err, "Query succeeded. It should have failed.") +} + +func addNewUserToGroup(userName, password, groupName string) error { + createGuardian := exec.Command("dgraph", "acl", "add", "-a", dgraphEndpoint, + "-u", userName, "-p", password, "-x", "password") + if err := createGuardian.Run(); err != nil { + return err + } + + makeGuardian := exec.Command("dgraph", "acl", "mod", "-a", dgraphEndpoint, "-u", "guardian", + "-l", x.GuardiansId, "-x", "password") + if err := makeGuardian.Run(); err != nil { + return err + } + + return nil +} + +func removeUserFromGroups(userName string) error { + removeUser := exec.Command("dgraph", "acl", "mod", "-a", dgraphEndpoint, "-u", userName, + "-l", "", "-x", "password") + return removeUser.Run() +} diff --git a/ee/acl/run_ee.go b/ee/acl/run_ee.go index 949eb7d072f..90d4d61d8e2 100644 --- a/ee/acl/run_ee.go +++ b/ee/acl/run_ee.go @@ -27,7 +27,8 @@ var ( CmdAcl x.SubCommand ) -const gPassword = "gpassword" +const gName = "guardian_name" +const gPassword = "guardian_password" const defaultGroupList = "dgraph-unused-group" func init() { @@ -38,7 +39,8 @@ func init() { flag := CmdAcl.Cmd.PersistentFlags() flag.StringP("alpha", "a", "127.0.0.1:9080", "Dgraph Alpha gRPC server address") - flag.StringP(gPassword, "x", "", "Groot password to authorize this operation") + flag.StringP(gName, "w", x.GrootId, "Guardian username performing this operation") + flag.StringP(gPassword, "x", "", "Guardian password to authorize this operation") // TLS configuration x.RegisterClientTLSFlags(flag) diff --git a/ee/acl/utils.go b/ee/acl/utils.go index 12454e8a6df..48a025c1edb 100644 --- a/ee/acl/utils.go +++ b/ee/acl/utils.go @@ -164,7 +164,7 @@ func getClientWithAdminCtx(conf *viper.Viper) (*dgo.Dgraph, x.CloseFunc, error) dg, closeClient := x.GetDgraphClient(conf, false) err := x.GetPassAndLogin(dg, &x.CredOpt{ Conf: conf, - UserID: x.GrootId, + UserID: conf.GetString(gName), PasswordOpt: gPassword, }) if err != nil { @@ -175,7 +175,7 @@ func getClientWithAdminCtx(conf *viper.Viper) (*dgo.Dgraph, x.CloseFunc, error) // CreateUserNQuads creates the NQuads needed to store a user with the given ID and // password in the ACL system. -func CreateUserNQuads(userId string, password string) []*api.NQuad { +func CreateUserNQuads(userId, password string) []*api.NQuad { return []*api.NQuad{ { Subject: "_:newuser", @@ -194,3 +194,19 @@ func CreateUserNQuads(userId string, password string) []*api.NQuad { }, } } + +// CreateGroupNQuads cretes NQuads needed to store a group with the give ID. +func CreateGroupNQuads(groupId string) []*api.NQuad { + return []*api.NQuad{ + { + Subject: "_:newgroup", + Predicate: "dgraph.xid", + ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: groupId}}, + }, + { + Subject: "_:newgroup", + Predicate: "dgraph.type", + ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: "Group"}}, + }, + } +} diff --git a/x/x.go b/x/x.go index 6c6ba98c1c1..d8cc1f858e4 100644 --- a/x/x.go +++ b/x/x.go @@ -101,6 +101,8 @@ const ( // GrootId is the ID of the admin user for ACLs. GrootId = "groot" + // GuardiansId is the ID of the admin group for ACLs. + GuardiansId = "guardians" // AclPredicates is the JSON representation of the predicates reserved for use // by the ACL system. AclPredicates = ` @@ -734,3 +736,13 @@ func GetPassAndLogin(dg *dgo.Dgraph, opt *CredOpt) error { // update the context so that it has the admin jwt token return nil } + +func IsGuardian(groups []string) bool { + for _, group := range groups { + if group == GuardiansId { + return true + } + } + + return false +}