summaryrefslogtreecommitdiff
path: root/jstests/serverless/native_tenant_data_isolation_kill_op.js
blob: 445a19a15d60b503089cacc938a97d04be6e69fb (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
// Test that we can create and auth a tenant (user) using a security token.
// Then create an op (insert) for that tenant, find it using `currentOp` and kill it using `killOp`.

(function() {
"use strict";

load('jstests/aggregation/extras/utils.js');     // For arrayEq()
load("jstests/libs/fail_point_util.js");         // For configureFailPoint()
load("jstests/libs/parallel_shell_helpers.js");  // For funWithArgs()

function killCurrentOpTest() {
    function operationToKillFunc(securityToken, dbName, colName) {
        db.getMongo()._setSecurityToken(securityToken);
        const insertCmdObj = {insert: colName, documents: [{_id: 0}]};
        assert.commandWorked(db.getSiblingDB(dbName).runCommand(insertCmdObj));
    }

    const rst = new ReplSetTest({
        nodes: 3,
        nodeOptions:
            {auth: '', setParameter: {multitenancySupport: true, featureFlagSecurityToken: true}}
    });
    rst.startSet({keyFile: 'jstests/libs/key1'});
    rst.initiate();

    const primary = rst.getPrimary();
    const adminDb = primary.getDB('admin');

    // Prepare a user for testing pass tenant via $tenant.
    // Must be authenticated as a user with ActionType::useTenant in order to use $tenant.
    assert.commandWorked(adminDb.runCommand({createUser: 'admin', pwd: 'pwd', roles: ['root']}));
    assert(adminDb.auth('admin', 'pwd'));

    const kTenant = ObjectId();
    const kOtherTenant = ObjectId();
    const kDbName = 'myDb';
    const kCollName = "currOpColl";

    // Create a user for kTenant and its security token.
    const securityToken =
        _createSecurityToken({user: "userTenant1", db: '$external', tenant: kTenant});
    assert.commandWorked(primary.getDB('$external').runCommand({
        createUser: "userTenant1",
        '$tenant': kTenant,
        roles:
            [{role: 'dbAdminAnyDatabase', db: 'admin'}, {role: 'readWriteAnyDatabase', db: 'admin'}]
    }));

    // Create a different tenant to test that one tenant can't see or kill other tenant's op.
    const securityTokenOtherTenant =
        _createSecurityToken({user: "userTenant2", db: '$external', tenant: kOtherTenant});
    assert.commandWorked(primary.getDB('$external').runCommand({
        createUser: "userTenant2",
        '$tenant': kOtherTenant,
        roles:
            [{role: 'dbAdminAnyDatabase', db: 'admin'}, {role: 'readWriteAnyDatabase', db: 'admin'}]
    }));

    const tokenConn = new Mongo(primary.host);
    tokenConn._setSecurityToken(securityToken);

    // test that the current tenant can see and kill his current op.
    {
        const findCurrentOpCmd = {
            aggregate: 1,
            pipeline: [{$currentOp: {allUsers: false}}, {$match: {op: "insert"}}],
            cursor: {}
        };

        const createCollFP = configureFailPoint(primary, "hangBeforeLoggingCreateCollection");

        const parallelShell = startParallelShell(
            funWithArgs(operationToKillFunc, securityToken, kDbName, kCollName), primary.port);

        createCollFP.wait();

        // find the current insert op that's being blocked due to the failpoint.
        let findCurrentOpRes = tokenConn.getDB("admin").runCommand(findCurrentOpCmd);
        assert.eq(findCurrentOpRes.cursor.firstBatch.length, 1, tojson(findCurrentOpRes));
        const opIdToKill = findCurrentOpRes.cursor.firstBatch[0].opid;

        // Try to kill the op with a different tenant / security token. Fails due to Unauthorized.
        tokenConn._setSecurityToken(securityTokenOtherTenant);
        assert.commandFailedWithCode(
            tokenConn.getDB("admin").runCommand({killOp: 1, op: opIdToKill}),
            ErrorCodes.Unauthorized);

        // Try to kill the op with a the same tenant / security token. Succeeds.
        tokenConn._setSecurityToken(securityToken);
        assert.commandWorked(tokenConn.getDB("admin").runCommand({killOp: 1, op: opIdToKill}));

        createCollFP.off();

        // the current op was killed therefor the thread will throw an exception and wil return
        // code 252.
        const exitCode = parallelShell({checkExitSuccess: false});
        assert.neq(0, exitCode, "Expected shell to exit with failure due to operation kill");

        // we should no longer have an operation for that tenant.
        findCurrentOpRes = tokenConn.getDB("admin").runCommand(findCurrentOpCmd);
        assert.eq(findCurrentOpRes.cursor.firstBatch.length, 0, tojson(findCurrentOpRes));
    }

    rst.stopSet();
}
killCurrentOpTest();
})();