Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add guardians group with full authorization #4447

Merged
merged 10 commits into from
Dec 24, 2019
94 changes: 61 additions & 33 deletions edgraph/access_ee.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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
}
}
Expand Down
13 changes: 1 addition & 12 deletions ee/acl/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
77 changes: 77 additions & 0 deletions ee/acl/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <unauthpred> \"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> <unauthpred> \"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()
}
6 changes: 4 additions & 2 deletions ee/acl/run_ee.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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)
Expand Down
20 changes: 18 additions & 2 deletions ee/acl/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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",
Expand All @@ -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"}},
},
}
}
12 changes: 12 additions & 0 deletions x/x.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `
Expand Down Expand Up @@ -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
}