summaryrefslogtreecommitdiff
path: root/jstests/auth/pinned_users.js
blob: f57bfa85f74d9bbc9fedbc8574b65183e2aa50c9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/*
 * This test checks that we can recover from an operation that has taken out a write lock
 * for a long time (potentially deadlocking the server). This comes out of a number of BF's
 * related to jstests/core/mr_killop.js in the parallel suite (see BF-6259 for the full
 * write-up).
 *
 * @tags: [requires_replication]
 *
 */
(function() {
    'use strict';
    jsTest.setOption("enableTestCommands", true);
    // Start a mongod with the user cache size set to zero, so we know that users who have
    // logged out always get fetched cleanly from disk.
    const rs = new ReplSetTest({
        nodes: 3,
        nodeOptions: {auth: "", setParameter: "authorizationManagerCacheSize=0"},
        keyFile: "jstests/libs/key1"
    });

    rs.startSet();
    rs.initiate();
    const mongod = rs.getPrimary();
    const admin = mongod.getDB("admin");

    admin.createUser({user: "admin", pwd: "admin", roles: ["root"]});
    admin.auth("admin", "admin");

    // Mark the "admin2" user as pinned in memory, we'll use this later on to recover from
    // the deadlock
    assert.commandWorked(admin.runCommand({
        setParameter: 1,
        logLevel: 2,
        authorizationManagerPinnedUsers: [
            {user: "admin2", db: "admin"},
        ],
    }));

    admin.createUser({user: "admin2", pwd: "admin", roles: ["root"]});

    let secondConn = new Mongo(mongod.host);
    let secondAdmin = secondConn.getDB("admin");
    secondAdmin.auth("admin2", "admin");

    // Invalidate the user cache so we know only "admin" is in there
    assert.commandWorked(admin.runCommand({invalidateUserCache: 1}));
    assert.soon(function() {
        let cacheContents = admin.aggregate([{$listCachedAndActiveUsers: {}}]).toArray();
        print("User cache after initialization: ", tojson(cacheContents));

        const admin2Doc = sortDoc({"username": "admin2", "db": "admin", "active": true});
        return cacheContents.some((doc) => friendlyEqual(admin2Doc, sortDoc(doc)));
    });

    const waitForCommand = function(waitingFor, opFilter) {
        let opId = -1;
        assert.soon(function() {
            print(`Checking for ${waitingFor}`);
            const curopRes = admin.currentOp();
            assert.commandWorked(curopRes);
            const foundOp = curopRes["inprog"].filter(opFilter);

            if (foundOp.length == 1) {
                opId = foundOp[0]["opid"];
            }
            return (foundOp.length == 1);
        });
        return opId;
    };

    // The deadlock happens in two phases. First we run a command that acquires a read lock and
    // holds it for forever.
    let readLockShell = startParallelShell(function() {
        assert.eq(db.getSiblingDB("admin").auth("admin", "admin"), 1);
        assert.commandFailed(db.adminCommand(
            {sleep: 1, secs: 500, lock: "r", lockTarget: "admin", $comment: "Read lock sleep"}));
    }, mongod.port);

    // Wait for that command to appear in currentOp
    const readID = waitForCommand(
        "readlock",
        op => (op["ns"] == "admin.$cmd" && op["command"]["$comment"] == "Read lock sleep"));

    // Then we run a command that tries to acquire a write lock, which will wait for forever
    // because we're already holding a read lock, but will also prevent any new read locks from
    // being taken.
    let writeLockShell = startParallelShell(function() {
        assert.eq(db.getSiblingDB("admin").auth("admin", "admin"), 1);
        assert.commandFailed(db.adminCommand(
            {sleep: 1, secs: 500, lock: "w", lockTarget: "admin", $comment: "Write lock sleep"}));
    }, mongod.port);

    // Wait for that to appear in currentOp
    const writeID = waitForCommand(
        "writeLock",
        op => (op["ns"] == "admin.$cmd" && op["command"]["$comment"] == "Write lock sleep"));

    print("killing ops and moving on!");

    // If "admin2" wasn't pinned in memory, then these would hang.
    assert.commandWorked(secondAdmin.currentOp());
    assert.commandWorked(secondAdmin.killOp(readID));
    assert.commandWorked(secondAdmin.killOp(writeID));

    readLockShell();
    writeLockShell();

    admin.logout();
    secondAdmin.logout();
    rs.stopSet();
})();

// This checks that removing a user document actually unpins a user. This is a round-about way
// of making sure that updates to the authz manager by the opObserver correctly invalidates the
// cache and that pinned users don't stick around after they're removed.
(function() {
    'use strict';
    jsTest.setOption("enableTestCommands", true);
    // Start a mongod with the user cache size set to zero, so we know that users who have
    // logged out always get fetched cleanly from disk.
    const mongod =
        MongoRunner.runMongod({auth: "", setParameter: "authorizationManagerCacheSize=0"});
    let admin = mongod.getDB("admin");

    admin.createUser({user: "admin", pwd: "admin", roles: ["root"]});
    admin.auth("admin", "admin");

    // Mark the "admin2" user as pinned in memory
    assert.commandWorked(admin.runCommand({
        setParameter: 1,
        logLevel: 2,
        authorizationManagerPinnedUsers: [
            {user: "admin2", db: "admin"},
        ],
    }));

    admin.createUser({user: "admin2", pwd: "admin", roles: ["root"]});

    // Invalidate the user cache so we know only "admin" is in there
    assert.commandWorked(admin.runCommand({invalidateUserCache: 1}));
    print("User cache after initialization: ",
          tojson(admin.aggregate([{$listCachedAndActiveUsers: {}}]).toArray()));

    assert.commandWorked(admin.getCollection("system.users").remove({user: "admin2"}));

    print("User cache after removing user doc: ",
          tojson(admin.aggregate([{$listCachedAndActiveUsers: {}}]).toArray()));

    assert.eq(admin.auth("admin2", "admin"), 0);
    MongoRunner.stopMongod(mongod);
})();

// This checks that clearing the pinned user list actually unpins a user.
(function() {
    'use strict';
    jsTest.setOption("enableTestCommands", true);
    // Start a mongod with the user cache size set to zero, so we know that users who have
    // logged out always get fetched cleanly from disk.
    const mongod =
        MongoRunner.runMongod({auth: "", setParameter: "authorizationManagerCacheSize=0"});
    let admin = mongod.getDB("admin");

    admin.createUser({user: "admin", pwd: "admin", roles: ["root"]});
    admin.auth("admin", "admin");

    // Mark the "admin2" user as pinned in memory
    assert.commandWorked(admin.runCommand({
        setParameter: 1,
        logLevel: 2,
        authorizationManagerPinnedUsers: [
            {user: "admin2", db: "admin"},
        ],
    }));

    admin.createUser({user: "admin2", pwd: "admin", roles: ["root"]});
    assert.soon(function() {
        let cacheContents = admin.aggregate([{$listCachedAndActiveUsers: {}}]).toArray();
        print("User cache after initialization: ", tojson(cacheContents));

        const admin2Doc = sortDoc({"username": "admin2", "db": "admin", "active": true});
        return cacheContents.some((doc) => friendlyEqual(admin2Doc, sortDoc(doc)));
    });

    // Clear the pinned users list
    assert.commandWorked(admin.runCommand({setParameter: 1, authorizationManagerPinnedUsers: []}));

    // Check that admin2 gets removed from the cache
    assert.commandWorked(admin.runCommand({invalidateUserCache: 1}));
    assert.soon(function() {
        let cacheContents = admin.aggregate([{$listCachedAndActiveUsers: {}}]).toArray();
        print("User cache after initialization: ", tojson(cacheContents));

        const admin2Doc = sortDoc({"username": "admin2", "db": "admin", "active": true});
        return !cacheContents.some((doc) => friendlyEqual(admin2Doc, sortDoc(doc)));
    });

    MongoRunner.stopMongod(mongod);
})();