Skip to content

Commit ceb4fec

Browse files
Merge commit from fork
* Add restoreTenant to the middleware config Add a test for all MW configurations * Remove partial comment
1 parent 8d1c982 commit ceb4fec

2 files changed

Lines changed: 116 additions & 0 deletions

File tree

graphql/admin/admin.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ var (
522522
"export": stdAdminMutMWs, // dgraph handles the export for other namespaces by guardian of galaxy
523523
"login": minimalAdminMutMWs,
524524
"restore": gogMutMWs,
525+
"restoreTenant": gogMutMWs,
525526
"shutdown": gogMutMWs,
526527
"removeNode": gogMutMWs,
527528
"moveTablet": gogMutMWs,

graphql/admin/admin_auth_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* SPDX-FileCopyrightText: © 2017-2025 Istari Digital, Inc.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package admin
7+
8+
import (
9+
"reflect"
10+
"testing"
11+
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
15+
"github.com/dgraph-io/dgraph/v25/graphql/resolve"
16+
)
17+
18+
// mwPointer returns the function pointer for a middleware so it can be compared
19+
// by identity rather than value (Go prohibits direct function equality checks).
20+
func mwPointer(mw resolve.MutationMiddleware) uintptr {
21+
return reflect.ValueOf(mw).Pointer()
22+
}
23+
24+
func containsMW(mws resolve.MutationMiddlewares, target resolve.MutationMiddleware) bool {
25+
want := mwPointer(target)
26+
for _, mw := range mws {
27+
if mwPointer(mw) == want {
28+
return true
29+
}
30+
}
31+
return false
32+
}
33+
34+
// TestAdminMutationMiddlewareConfig asserts the security posture for every
35+
// registered admin mutation.
36+
//
37+
// Each mutation must be present in adminMutationMWConfig. An absent entry
38+
// causes the middleware chain to be empty, bypassing all authentication,
39+
// IP whitelisting, and audit logging (see resolve/middlewares.go: Then()).
40+
func TestAdminMutationMiddlewareConfig(t *testing.T) {
41+
type securityRequirements struct {
42+
// desc is shown in failure messages to explain why this mutation needs these middlewares.
43+
desc string
44+
// ipWhitelist: must include IpWhitelistingMW4Mutation
45+
ipWhitelist bool
46+
// superAdminAuth: must include GuardianOfTheGalaxyAuthMW4Mutation
47+
superAdminAuth bool
48+
// guardianAuth: must include GuardianAuthMW4Mutation
49+
guardianAuth bool
50+
// aclOnly: must include AclOnlyMW4Mutation
51+
aclOnly bool
52+
}
53+
54+
tests := map[string]securityRequirements{
55+
// Superadmin (Guardian-of-Galaxy) auth — highest privilege operations.
56+
"backup": {desc: "database backups", ipWhitelist: true, superAdminAuth: true},
57+
"config": {desc: "cluster config changes", ipWhitelist: true, superAdminAuth: true},
58+
"draining": {desc: "draining mode", ipWhitelist: true, superAdminAuth: true},
59+
"restore": {desc: "backup restore", ipWhitelist: true, superAdminAuth: true},
60+
"restoreTenant": { // CVE: previously absent from this map (CVSS 10.0)
61+
desc: "cross-namespace backup restore — accepts attacker-controlled URLs",
62+
ipWhitelist: true,
63+
superAdminAuth: true,
64+
},
65+
"shutdown": {desc: "node shutdown", ipWhitelist: true, superAdminAuth: true},
66+
"removeNode": {desc: "cluster topology change", ipWhitelist: true, superAdminAuth: true},
67+
"moveTablet": {desc: "tablet relocation", ipWhitelist: true, superAdminAuth: true},
68+
"assign": {desc: "UID/timestamp assignment", ipWhitelist: true, superAdminAuth: true},
69+
70+
// Superadmin + ACL — namespace lifecycle mutations.
71+
"addNamespace": {desc: "namespace creation", ipWhitelist: true, superAdminAuth: true, aclOnly: true},
72+
"deleteNamespace": {desc: "namespace deletion", ipWhitelist: true, superAdminAuth: true, aclOnly: true},
73+
"resetPassword": {desc: "password reset", ipWhitelist: true, superAdminAuth: true, aclOnly: true},
74+
75+
// Guardian auth — standard admin operations.
76+
"export": {desc: "data export", ipWhitelist: true, guardianAuth: true},
77+
"updateGQLSchema": {desc: "GraphQL schema update", ipWhitelist: true, guardianAuth: true},
78+
79+
// Minimal (IP whitelist + logging only) — dgraph handles auth internally for these.
80+
"login": {desc: "login (auth handled internally)", ipWhitelist: true},
81+
"addUser": {desc: "user management (dgraph handles guardian auth)", ipWhitelist: true},
82+
"addGroup": {desc: "group management (dgraph handles guardian auth)", ipWhitelist: true},
83+
"updateUser": {desc: "user management (dgraph handles guardian auth)", ipWhitelist: true},
84+
"updateGroup": {desc: "group management (dgraph handles guardian auth)", ipWhitelist: true},
85+
"deleteUser": {desc: "user management (dgraph handles guardian auth)", ipWhitelist: true},
86+
"deleteGroup": {desc: "group management (dgraph handles guardian auth)", ipWhitelist: true},
87+
}
88+
89+
for mutation, req := range tests {
90+
t.Run(mutation, func(t *testing.T) {
91+
mws, ok := adminMutationMWConfig[mutation]
92+
require.Truef(t, ok,
93+
"mutation %q (%s) is missing from adminMutationMWConfig — "+
94+
"absent entries bypass ALL authentication, IP whitelisting, and audit logging",
95+
mutation, req.desc)
96+
97+
if req.ipWhitelist {
98+
assert.Truef(t, containsMW(mws, resolve.IpWhitelistingMW4Mutation),
99+
"mutation %q (%s) must include IpWhitelistingMW4Mutation", mutation, req.desc)
100+
}
101+
if req.superAdminAuth {
102+
assert.Truef(t, containsMW(mws, resolve.GuardianOfTheGalaxyAuthMW4Mutation),
103+
"mutation %q (%s) must include GuardianOfTheGalaxyAuthMW4Mutation", mutation, req.desc)
104+
}
105+
if req.guardianAuth {
106+
assert.Truef(t, containsMW(mws, resolve.GuardianAuthMW4Mutation),
107+
"mutation %q (%s) must include GuardianAuthMW4Mutation", mutation, req.desc)
108+
}
109+
if req.aclOnly {
110+
assert.Truef(t, containsMW(mws, resolve.AclOnlyMW4Mutation),
111+
"mutation %q (%s) must include AclOnlyMW4Mutation", mutation, req.desc)
112+
}
113+
})
114+
}
115+
}

0 commit comments

Comments
 (0)