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