From f7b7d1f260da1fca9a6240fe4891ece390768ea8 Mon Sep 17 00:00:00 2001 From: emadum Date: Tue, 1 Sep 2020 19:37:34 -0400 Subject: [PATCH 1/2] refactor(kerberos): move MongoAuthProcess into driver --- lib/core/auth/gssapi.js | 184 ++++++++++++++++++++++++++-------------- 1 file changed, 122 insertions(+), 62 deletions(-) diff --git a/lib/core/auth/gssapi.js b/lib/core/auth/gssapi.js index c100e3030b..8a74b81b62 100644 --- a/lib/core/auth/gssapi.js +++ b/lib/core/auth/gssapi.js @@ -1,85 +1,92 @@ '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 Error(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); + } + ); }); }); }); @@ -87,5 +94,58 @@ class GSSAPI extends AuthProvider { }); } } - 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); + }); +} From c0d5424ba35dc53077238713e205bd6e993c593a Mon Sep 17 00:00:00 2001 From: emadum Date: Tue, 1 Sep 2020 20:01:21 -0400 Subject: [PATCH 2/2] Error => MongoError --- lib/core/auth/gssapi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/auth/gssapi.js b/lib/core/auth/gssapi.js index 8a74b81b62..66ad0276eb 100644 --- a/lib/core/auth/gssapi.js +++ b/lib/core/auth/gssapi.js @@ -46,7 +46,7 @@ class GSSAPI extends AuthProvider { `${serviceName}${process.platform === 'win32' ? '/' : '@'}${host}`, initOptions, (err, client) => { - if (err) return callback(new Error(err)); + if (err) return callback(new MongoError(err)); if (client == null) return callback(); this[kGssapiClient] = client; callback(undefined, handshakeDoc);