Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(kerberos): move MongoAuthProcess into driver #2535

Merged
merged 2 commits into from Sep 2, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
184 changes: 122 additions & 62 deletions lib/core/auth/gssapi.js
@@ -1,91 +1,151 @@
'use strict';
const dns = require('dns');

const AuthProvider = require('./auth_provider').AuthProvider;
const retrieveKerberos = require('../utils').retrieveKerberos;
const MongoError = require('../error').MongoError;

const kGssapiClient = Symbol('GSSAPI_CLIENT');
let kerberos;

class GSSAPI extends AuthProvider {
auth(authContext, callback) {
const connection = authContext.connection;
prepare(handshakeDoc, authContext, callback) {
const host = authContext.options.host;
const port = authContext.options.port;
const credentials = authContext.credentials;

if (!host || !port || !credentials) {
return callback(
new MongoError(
`Connection must specify: ${host ? 'host' : ''}, ${port ? 'port' : ''}, ${
credentials ? 'host' : 'credentials'
}.`
)
);
}
if (kerberos == null) {
try {
kerberos = retrieveKerberos();
} catch (e) {
return callback(e, null);
return callback(e);
}
}

// TODO: Destructure this
const username = credentials.username;
const password = credentials.password;
const mechanismProperties = credentials.mechanismProperties;
const gssapiServiceName =
const serviceName =
mechanismProperties['gssapiservicename'] ||
mechanismProperties['gssapiServiceName'] ||
'mongodb';

const MongoAuthProcess = kerberos.processes.MongoAuthProcess;
const authProcess = new MongoAuthProcess(
connection.host,
connection.port,
gssapiServiceName,
mechanismProperties
);

authProcess.init(username, password, err => {
if (err) return callback(err, false);

authProcess.transition('', (err, payload) => {
if (err) return callback(err, false);

const command = {
saslStart: 1,
mechanism: 'GSSAPI',
payload,
autoAuthorize: 1
};

connection.command('$external.$cmd', command, (err, result) => {
if (err) return callback(err, false);

const doc = result.result;
authProcess.transition(doc.payload, (err, payload) => {
if (err) return callback(err, false);
const command = {
saslContinue: 1,
conversationId: doc.conversationId,
payload
};

connection.command('$external.$cmd', command, (err, result) => {
if (err) return callback(err, false);

const doc = result.result;
authProcess.transition(doc.payload, (err, payload) => {
if (err) return callback(err, false);
const command = {
performGssapiCanonicalizeHostName(host, mechanismProperties, (err, host) => {
if (err) return callback(err);
const initOptions = {};
if (password != null) {
Object.assign(initOptions, { user: username, password: password });
}
kerberos.initializeClient(
`${serviceName}${process.platform === 'win32' ? '/' : '@'}${host}`,
initOptions,
(err, client) => {
if (err) return callback(new MongoError(err));
if (client == null) return callback();
this[kGssapiClient] = client;
callback(undefined, handshakeDoc);
}
);
});
}
auth(authContext, callback) {
const connection = authContext.connection;
const credentials = authContext.credentials;
if (credentials == null) return callback(new MongoError('credentials required'));
const username = credentials.username;
const client = this[kGssapiClient];
if (client == null) return callback(new MongoError('gssapi client missing'));
function externalCommand(command, cb) {
return connection.command('$external.$cmd', command, cb);
}
client.step('', (err, payload) => {
if (err) return callback(err);
externalCommand(saslStart(payload), (err, response) => {
const result = response.result;
if (err) return callback(err);
negotiate(client, 10, result.payload, (err, payload) => {
if (err) return callback(err);
externalCommand(saslContinue(payload, result.conversationId), (err, response) => {
const result = response.result;
if (err) return callback(err);
finalize(client, username, result.payload, (err, payload) => {
if (err) return callback(err);
externalCommand(
{
saslContinue: 1,
conversationId: doc.conversationId,
conversationId: result.conversationId,
payload
};

connection.command('$external.$cmd', command, (err, result) => {
if (err) return callback(err, false);

const response = result.result;
authProcess.transition(null, err => {
if (err) return callback(err, null);
callback(null, response);
});
});
});
},
(err, result) => {
if (err) return callback(err);
callback(undefined, result);
}
);
});
});
});
});
});
}
}

module.exports = GSSAPI;

function saslStart(payload) {
return {
saslStart: 1,
mechanism: 'GSSAPI',
payload,
autoAuthorize: 1
};
}
function saslContinue(payload, conversationId) {
return {
saslContinue: 1,
conversationId,
payload
};
}
function negotiate(client, retries, payload, callback) {
client.step(payload, (err, response) => {
// Retries exhausted, raise error
if (err && retries === 0) return callback(err);
// Adjust number of retries and call step again
if (err) return negotiate(client, retries - 1, payload, callback);
// Return the payload
callback(undefined, response || '');
});
}
function finalize(client, user, payload, callback) {
// GSS Client Unwrap
client.unwrap(payload, (err, response) => {
if (err) return callback(err);
// Wrap the response
client.wrap(response || '', { user }, (err, wrapped) => {
if (err) return callback(err);
// Return the payload
callback(undefined, wrapped);
});
});
}
function performGssapiCanonicalizeHostName(host, mechanismProperties, callback) {
const canonicalizeHostName =
typeof mechanismProperties.gssapiCanonicalizeHostName === 'boolean'
? mechanismProperties.gssapiCanonicalizeHostName
: false;
if (!canonicalizeHostName) return callback(undefined, host);
// Attempt to resolve the host name
dns.resolveCname(host, (err, r) => {
if (err) return callback(err);
// Get the first resolve host id
if (Array.isArray(r) && r.length > 0) {
return callback(undefined, r[0]);
}
callback(undefined, host);
});
}