File size: 5,831 Bytes
9756872
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveCname = exports.performGSSAPICanonicalizeHostName = exports.GSSAPI = exports.GSSAPICanonicalizationValue = void 0;
const dns = require("dns");
const deps_1 = require("../../deps");
const error_1 = require("../../error");
const utils_1 = require("../../utils");
const auth_provider_1 = require("./auth_provider");
/** @public */
exports.GSSAPICanonicalizationValue = Object.freeze({
    on: true,
    off: false,
    none: 'none',
    forward: 'forward',
    forwardAndReverse: 'forwardAndReverse'
});
async function externalCommand(connection, command) {
    const response = await connection.command((0, utils_1.ns)('$external.$cmd'), command);
    return response;
}
let krb;
class GSSAPI extends auth_provider_1.AuthProvider {
    async auth(authContext) {
        const { connection, credentials } = authContext;
        if (credentials == null) {
            throw new error_1.MongoMissingCredentialsError('Credentials required for GSSAPI authentication');
        }
        const { username } = credentials;
        const client = await makeKerberosClient(authContext);
        const payload = await client.step('');
        const saslStartResponse = await externalCommand(connection, saslStart(payload));
        const negotiatedPayload = await negotiate(client, 10, saslStartResponse.payload);
        const saslContinueResponse = await externalCommand(connection, saslContinue(negotiatedPayload, saslStartResponse.conversationId));
        const finalizePayload = await finalize(client, username, saslContinueResponse.payload);
        await externalCommand(connection, {
            saslContinue: 1,
            conversationId: saslContinueResponse.conversationId,
            payload: finalizePayload
        });
    }
}
exports.GSSAPI = GSSAPI;
async function makeKerberosClient(authContext) {
    const { hostAddress } = authContext.options;
    const { credentials } = authContext;
    if (!hostAddress || typeof hostAddress.host !== 'string' || !credentials) {
        throw new error_1.MongoInvalidArgumentError('Connection must have host and port and credentials defined.');
    }
    loadKrb();
    if ('kModuleError' in krb) {
        throw krb['kModuleError'];
    }
    const { initializeClient } = krb;
    const { username, password } = credentials;
    const mechanismProperties = credentials.mechanismProperties;
    const serviceName = mechanismProperties.SERVICE_NAME ?? 'mongodb';
    const host = await performGSSAPICanonicalizeHostName(hostAddress.host, mechanismProperties);
    const initOptions = {};
    if (password != null) {
        // TODO(NODE-5139): These do not match the typescript options in initializeClient
        Object.assign(initOptions, { user: username, password: password });
    }
    const spnHost = mechanismProperties.SERVICE_HOST ?? host;
    let spn = `${serviceName}${process.platform === 'win32' ? '/' : '@'}${spnHost}`;
    if ('SERVICE_REALM' in mechanismProperties) {
        spn = `${spn}@${mechanismProperties.SERVICE_REALM}`;
    }
    return await initializeClient(spn, initOptions);
}
function saslStart(payload) {
    return {
        saslStart: 1,
        mechanism: 'GSSAPI',
        payload,
        autoAuthorize: 1
    };
}
function saslContinue(payload, conversationId) {
    return {
        saslContinue: 1,
        conversationId,
        payload
    };
}
async function negotiate(client, retries, payload) {
    try {
        const response = await client.step(payload);
        return response || '';
    }
    catch (error) {
        if (retries === 0) {
            // Retries exhausted, raise error
            throw error;
        }
        // Adjust number of retries and call step again
        return await negotiate(client, retries - 1, payload);
    }
}
async function finalize(client, user, payload) {
    // GSS Client Unwrap
    const response = await client.unwrap(payload);
    return await client.wrap(response || '', { user });
}
async function performGSSAPICanonicalizeHostName(host, mechanismProperties) {
    const mode = mechanismProperties.CANONICALIZE_HOST_NAME;
    if (!mode || mode === exports.GSSAPICanonicalizationValue.none) {
        return host;
    }
    // If forward and reverse or true
    if (mode === exports.GSSAPICanonicalizationValue.on ||
        mode === exports.GSSAPICanonicalizationValue.forwardAndReverse) {
        // Perform the lookup of the ip address.
        const { address } = await dns.promises.lookup(host);
        try {
            // Perform a reverse ptr lookup on the ip address.
            const results = await dns.promises.resolvePtr(address);
            // If the ptr did not error but had no results, return the host.
            return results.length > 0 ? results[0] : host;
        }
        catch (error) {
            // This can error as ptr records may not exist for all ips. In this case
            // fallback to a cname lookup as dns.lookup() does not return the
            // cname.
            return await resolveCname(host);
        }
    }
    else {
        // The case for forward is just to resolve the cname as dns.lookup()
        // will not return it.
        return await resolveCname(host);
    }
}
exports.performGSSAPICanonicalizeHostName = performGSSAPICanonicalizeHostName;
async function resolveCname(host) {
    // Attempt to resolve the host name
    try {
        const results = await dns.promises.resolveCname(host);
        // Get the first resolved host id
        return results.length > 0 ? results[0] : host;
    }
    catch {
        return host;
    }
}
exports.resolveCname = resolveCname;
/**
 * Load the Kerberos library.
 */
function loadKrb() {
    if (!krb) {
        krb = (0, deps_1.getKerberos)();
    }
}
//# sourceMappingURL=gssapi.js.map