summaryrefslogtreecommitdiff
path: root/jstests/auth/speculative-auth-replset.js
blob: 9f36444a0204d750a7e0d7579b941a55d443cb3d (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
// Verify that replica sets can speculatively authenticate
// to each other during intra-cluster communication.
// @tags: [requires_replication]

(function() {
'use strict';

const kAuthenticationSuccessfulLogId = 5286306;
const kAuthenticationFailedLogId = 5286307;

function countAuthInLog(conn) {
    let logCounts = {speculative: 0, cluster: 0, speculativeCluster: 0};

    checkLog.getGlobalLog(conn).forEach((line) => {
        // Iterate through the log and verify our auth.
        const entry = JSON.parse(line);
        if (entry.id === kAuthenticationSuccessfulLogId) {
            // Successful auth.
            if (entry.attr.isSpeculative) {
                logCounts.speculative += 1;
            }
            if (entry.attr.isClusterMember) {
                logCounts.cluster += 1;
            }
            if (entry.attr.isSpeculative && entry.attr.isClusterMember) {
                logCounts.speculativeCluster += 1;
            }
        } else if (entry.id === kAuthenticationFailedLogId) {
            // Authentication can fail legitimately because the secondary abandons the connection
            // during shutdown.
            assert.eq(entry.attr.error.code, ErrorCodes.AuthenticationAbandoned);
        } else {
            // Irrelevant.
            return;
        }
    });

    print(`Found log entries for authentication in the following amounts: ${tojson(logCounts)}`);
    return logCounts;
}

const rst = new ReplSetTest({nodes: 1, keyFile: 'jstests/libs/key1'});
rst.startSet();
rst.initiate();
rst.awaitReplication();

const admin = rst.getPrimary().getDB('admin');
admin.createUser({user: 'admin', pwd: 'pwd', roles: ['root']});
admin.auth('admin', 'pwd');

assert.commandWorked(admin.setLogLevel(3, 'accessControl'));

function getMechStats(db) {
    return assert.commandWorked(db.runCommand({serverStatus: 1}))
        .security.authentication.mechanisms;
}

// Capture statistics after a fresh instantiation of a 1-node replica set.
// initialMechStats contains stats state for the test setup (e.g. shell authentication) actions
// that will have incremented the internal counters but are not relevant to the functionality under
// test
const initialMechStats = getMechStats(admin);

printjson(initialMechStats);

assert(initialMechStats['SCRAM-SHA-256'] !== undefined);

// We've made no client connections for which speculation was possible,
// because we authenticated as `admin` using the shell helpers.
// Because of the simple cluster topology, we should have no intracluster authentication attempts.
Object.keys(initialMechStats).forEach(function(mech) {
    const specStats = initialMechStats[mech].speculativeAuthenticate;
    const clusterStats = initialMechStats[mech].clusterAuthenticate;

    // No speculation has occured
    assert.eq(specStats.received, 0);

    // Statistics should be consistent for all mechanisms
    assert.eq(specStats.received, specStats.successful);
    assert.eq(clusterStats.received, clusterStats.successful);
});

{
    // Add and remove a node to force intra-cluster traffic, and authentication attempts.
    // Removal will require force-reconfig because the original node will not constitute a
    // "majority" of the resulting two node replicaset.
    const singleNodeConfig = rst.getReplSetConfigFromNode();

    const newNode = rst.add({});
    rst.reInitiate();
    rst.waitForState(newNode, ReplSetTest.State.SECONDARY);
    rst.awaitReplication();

    rst.stop(newNode);
    rst.remove(newNode);
    admin.auth('admin', 'pwd');
    singleNodeConfig.version = rst.getReplSetConfigFromNode(0).version + 1;
    assert.commandWorked(admin.runCommand({replSetReconfig: singleNodeConfig, force: true}));
    rst.awaitReplication();
}

{
    // Capture new statistics, and assert that they're consistent.
    const newMechStats = getMechStats(admin);
    printjson(newMechStats);

    // Speculative and cluster statistics should be incremented by intracluster auth.
    assert.gt(newMechStats["SCRAM-SHA-256"].speculativeAuthenticate.successful,
              initialMechStats["SCRAM-SHA-256"].speculativeAuthenticate.successful);
    assert.gt(newMechStats["SCRAM-SHA-256"].clusterAuthenticate.successful,
              initialMechStats["SCRAM-SHA-256"].clusterAuthenticate.successful);

    // Speculative and cluster auth counts should align with the authentication events in the server
    // log
    const logCounts = countAuthInLog(admin);

    assert.eq(logCounts.speculative,
              newMechStats["SCRAM-SHA-256"].speculativeAuthenticate.successful -
                  initialMechStats["SCRAM-SHA-256"].speculativeAuthenticate.successful);

    assert.eq(logCounts.cluster, newMechStats["SCRAM-SHA-256"].clusterAuthenticate.successful);

    assert.gt(logCounts.speculativeCluster,
              0,
              "Expected to observe at least one speculative cluster authentication attempt");
}

admin.logout();
rst.stopSet();
}());