You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
5.6 KiB
JavaScript
153 lines
5.6 KiB
JavaScript
"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) {
|
|
return connection.commandAsync((0, utils_1.ns)('$external.$cmd'), command, undefined);
|
|
}
|
|
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 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 negotiate(client, retries - 1, payload);
|
|
}
|
|
}
|
|
async function finalize(client, user, payload) {
|
|
// GSS Client Unwrap
|
|
const response = await client.unwrap(payload);
|
|
return 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 resolveCname(host);
|
|
}
|
|
}
|
|
else {
|
|
// The case for forward is just to resolve the cname as dns.lookup()
|
|
// will not return it.
|
|
return 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
|