Skip to content

Commit

Permalink
feat(cloud): add shared-instance flag in limit superflag in alpha (#7770
Browse files Browse the repository at this point in the history
) (#8625)

This PR adds a shared-instance flag to --limit superflag.
When set to true (false by default), it will:

- Restrict access to any of the ACL operations like Login,
add/remove/update user from non-galaxy namespaces.
- Prevent the leaking of environment variables for minio and aws.

(cherry picked from commit 5f3cece)
  • Loading branch information
all-seeing-code authored Feb 8, 2023
1 parent e4ff3a0 commit a153ed5
Show file tree
Hide file tree
Showing 14 changed files with 438 additions and 95 deletions.
7 changes: 5 additions & 2 deletions dgraph/cmd/alpha/run.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2022 Dgraph Labs, Inc. and Contributors
* Copyright 2017-2023 Dgraph Labs, Inc. and Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -205,6 +205,9 @@ they form a Raft group and provide synchronous replication.
"worker in a failed state. Use -1 to retry infinitely.").
Flag("txn-abort-after", "Abort any pending transactions older than this duration."+
" The liveness of a transaction is determined by its last mutation.").
Flag("shared-instance", "When set to true, it disables ACLs for non-galaxy users. "+
"It expects the access JWT to be constructed outside dgraph for non-galaxy users as "+
"login is denied to them. Additionally, this disables access to environment variables for minio, aws, etc.").
String())

flag.String("graphql", worker.GraphQLDefaults, z.NewSuperFlagHelp(worker.GraphQLDefaults).
Expand Down Expand Up @@ -628,7 +631,6 @@ func run() {
pstoreBlockCacheSize, pstoreIndexCacheSize)
bopts := badger.DefaultOptions("").FromSuperFlag(worker.BadgerDefaults + cacheOpts).
FromSuperFlag(Alpha.Conf.GetString("badger"))

security := z.NewSuperFlag(Alpha.Conf.GetString("security")).MergeAndCheckDefault(
worker.SecurityDefaults)
conf := audit.GetAuditConf(Alpha.Conf.GetString("audit"))
Expand Down Expand Up @@ -717,6 +719,7 @@ func run() {
x.Config.LimitNormalizeNode = int(x.Config.Limit.GetInt64("normalize-node"))
x.Config.QueryTimeout = x.Config.Limit.GetDuration("query-timeout")
x.Config.MaxRetries = x.Config.Limit.GetInt64("max-retries")
x.Config.SharedInstance = x.Config.Limit.GetBool("shared-instance")

x.Config.GraphQL = z.NewSuperFlag(Alpha.Conf.GetString("graphql")).MergeAndCheckDefault(
worker.GraphQLDefaults)
Expand Down
79 changes: 51 additions & 28 deletions edgraph/access_ee.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ type predsAndvars struct {
func (s *Server) Login(ctx context.Context,
request *api.LoginRequest) (*api.Response, error) {

if !shouldAllowAcls(request.GetNamespace()) {
return nil, errors.New("operation is not allowed in shared cloud mode")
}

if err := x.HealthCheck(); err != nil {
return nil, err
}
Expand Down Expand Up @@ -628,18 +632,15 @@ type authPredResult struct {
}

func authorizePreds(ctx context.Context, userData *userData, preds []string,
aclOp *acl.Operation) (*authPredResult, error) {
aclOp *acl.Operation) *authPredResult {

ns, err := x.ExtractNamespace(ctx)
if err != nil {
return nil, errors.Wrapf(err, "While authorizing preds")
}
if !worker.AclCachePtr.Loaded() {
RefreshACLs(ctx)
}

userId := userData.userId
groupIds := userData.groupIds
ns := userData.namespace
blockedPreds := make(map[string]struct{})
for _, pred := range preds {
nsPred := x.NamespaceAttr(ns, pred)
Expand All @@ -651,15 +652,13 @@ func authorizePreds(ctx context.Context, userData *userData, preds []string,
operation: aclOp,
allowed: false,
})

blockedPreds[pred] = struct{}{}
}
}

if worker.HasAccessToAllPreds(ns, groupIds, aclOp) {
// Setting allowed to nil allows access to all predicates. Note that the access to ACL
// predicates will still be blocked.
return &authPredResult{allowed: nil, blocked: blockedPreds}, nil
return &authPredResult{allowed: nil, blocked: blockedPreds}
}
// User can have multiple permission for same predicate, add predicate
allowedPreds := make([]string, 0, len(worker.AclCachePtr.GetUserPredPerms(userId)))
Expand All @@ -669,7 +668,7 @@ func authorizePreds(ctx context.Context, userData *userData, preds []string,
allowedPreds = append(allowedPreds, predicate)
}
}
return &authPredResult{allowed: allowedPreds, blocked: blockedPreds}, nil
return &authPredResult{allowed: allowedPreds, blocked: blockedPreds}
}

// authorizeAlter parses the Schema in the operation and authorizes the operation
Expand Down Expand Up @@ -724,10 +723,7 @@ func authorizeAlter(ctx context.Context, op *api.Operation) error {
"only guardians are allowed to drop all data, but the current user is %s", userId)
}

result, err := authorizePreds(ctx, userData, preds, acl.Modify)
if err != nil {
return nil
}
result := authorizePreds(ctx, userData, preds, acl.Modify)
if len(result.blocked) > 0 {
var msg strings.Builder
for key := range result.blocked {
Expand Down Expand Up @@ -836,12 +832,17 @@ func authorizeMutation(ctx context.Context, gmu *dql.Mutation) error {
case isAclPredMutation(gmu.Del):
return errors.Errorf("ACL predicates can't be deleted")
}
if !shouldAllowAcls(userData.namespace) {
for _, pred := range preds {
if x.IsAclPredicate(pred) {
return status.Errorf(codes.PermissionDenied,
"unauthorized to mutate acl predicates: %s\n", pred)
}
}
}
return nil
}
result, err := authorizePreds(ctx, userData, preds, acl.Write)
if err != nil {
return err
}
result := authorizePreds(ctx, userData, preds, acl.Write)
if len(result.blocked) > 0 {
var msg strings.Builder
for key := range result.blocked {
Expand Down Expand Up @@ -949,6 +950,21 @@ func logAccess(log *accessEntry) {
}
}

func blockedPreds(preds []string) map[string]struct{} {
blocked := make(map[string]struct{})
for _, pred := range preds {
if x.IsAclPredicate(pred) {
blocked[pred] = struct{}{}
}
}
return blocked
}

// With shared instance enabled, we don't allow ACL operations from any of the non-galaxy namespace.
func shouldAllowAcls(ns uint64) bool {
return !x.Config.SharedInstance || ns == x.GalaxyNamespace
}

// authorizeQuery authorizes the query using the aclCachePtr. It will silently drop all
// unauthorized predicates from query.
// At this stage, namespace is not attached in the predicates.
Expand All @@ -960,6 +976,7 @@ func authorizeQuery(ctx context.Context, parsedReq *dql.Result, graphql bool) er

var userId string
var groupIds []string
var namespace uint64
predsAndvars := parsePredsFromQuery(parsedReq.Query)
preds := predsAndvars.preds
varsToPredMap := predsAndvars.vars
Expand All @@ -979,14 +996,18 @@ func authorizeQuery(ctx context.Context, parsedReq *dql.Result, graphql bool) er

userId = userData.userId
groupIds = userData.groupIds
namespace = userData.namespace

if x.IsGuardian(groupIds) {
// Members of guardian groups are allowed to query anything.
return nil, nil, nil
if shouldAllowAcls(userData.namespace) {
// Members of guardian groups are allowed to query anything.
return nil, nil, nil
}
return blockedPreds(preds), nil, nil
}

result, err := authorizePreds(ctx, userData, preds, acl.Read)
return result.blocked, result.allowed, err
result := authorizePreds(ctx, userData, preds, acl.Read)
return result.blocked, result.allowed, nil
}

blockedPreds, allowedPreds, err := doAuthorizeQuery()
Expand All @@ -1007,7 +1028,7 @@ func authorizeQuery(ctx context.Context, parsedReq *dql.Result, graphql bool) er
if len(blockedPreds) != 0 {
// For GraphQL requests, we allow filtered access to the ACL predicates.
// Filter for user_id and group_id is applied for the currently logged in user.
if graphql {
if graphql && shouldAllowAcls(namespace) {
for _, gq := range parsedReq.Query {
addUserFilterToQuery(gq, userId, groupIds)
}
Expand Down Expand Up @@ -1068,11 +1089,14 @@ func authorizeSchemaQuery(ctx context.Context, er *query.ExecutionResult) error

groupIds := userData.groupIds
if x.IsGuardian(groupIds) {
// Members of guardian groups are allowed to query anything.
return nil, nil
if shouldAllowAcls(userData.namespace) {
// Members of guardian groups are allowed to query anything.
return nil, nil
}
return blockedPreds(preds), nil
}
result, err := authorizePreds(ctx, userData, preds, acl.Read)
return result.blocked, err
result := authorizePreds(ctx, userData, preds, acl.Read)
return result.blocked, nil
}

// find the predicates which are blocked for the schema query
Expand Down Expand Up @@ -1115,8 +1139,7 @@ func AuthGuardianOfTheGalaxy(ctx context.Context) error {
}
ns, err := x.ExtractNamespaceFrom(ctx)
if err != nil {
return status.Error(codes.Unauthenticated,
"AuthGuardianOfTheGalaxy: extracting jwt token, error: "+err.Error())
return errors.Wrap(err, "Authorize guardian of the galaxy, extracting jwt token, error:")
}
if ns != 0 {
return status.Error(
Expand Down
10 changes: 7 additions & 3 deletions systest/acl/restore/acl_restore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ func disableDraining(t *testing.T) {
b, err := json.Marshal(params)
require.NoError(t, err)

token := testutil.Login(t, &testutil.LoginParams{UserID: "groot", Passwd: "password", Namespace: 0})
token, err := testutil.Login(t, &testutil.LoginParams{UserID: "groot", Passwd: "password", Namespace: 0})
require.NoError(t, err, "login failed")

client := &http.Client{}
req, err := http.NewRequest("POST", testutil.AdminUrl(), bytes.NewBuffer(b))
Expand Down Expand Up @@ -70,7 +71,9 @@ func sendRestoreRequest(t *testing.T, location, backupId string, backupNum int)
},
}

token := testutil.Login(t, &testutil.LoginParams{UserID: "groot", Passwd: "password", Namespace: 0})
token, err := testutil.Login(t, &testutil.LoginParams{UserID: "groot", Passwd: "password", Namespace: 0})
require.NoError(t, err, "login failed")

resp := testutil.MakeGQLRequestWithAccessJwt(t, params, token.AccessJwt)
resp.RequireNoGraphQLErrors(t)

Expand All @@ -97,8 +100,9 @@ func TestAclCacheRestore(t *testing.T) {
sendRestoreRequest(t, "/backups", "vibrant_euclid5", 1)
testutil.WaitForRestore(t, dg, testutil.SockAddrHttp)

token := testutil.Login(t,
token, err := testutil.Login(t,
&testutil.LoginParams{UserID: "alice1", Passwd: "password", Namespace: 0})
require.NoError(t, err, "login failed")
params := &common.GraphQLParams{
Query: `query{
queryPerson{
Expand Down
6 changes: 3 additions & 3 deletions systest/backup/multi-tenancy/backup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ func TestBackupMultiTenancy(t *testing.T) {
dg := testutil.DgClientWithLogin(t, "groot", "password", x.GalaxyNamespace)
testutil.DropAll(t, dg)

galaxyCreds := &testutil.LoginParams{
UserID: "groot", Passwd: "password", Namespace: x.GalaxyNamespace}
galaxyToken := testutil.Login(t, galaxyCreds)
galaxyCreds := &testutil.LoginParams{UserID: "groot", Passwd: "password", Namespace: x.GalaxyNamespace}
galaxyToken, err := testutil.Login(t, galaxyCreds)
require.NoError(t, err, "login failed")

// Create a new namespace
ns1, err := testutil.CreateNamespaceWithRetry(t, galaxyToken)
Expand Down
Loading

0 comments on commit a153ed5

Please sign in to comment.