You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					188 lines
				
				7.4 KiB
			
		
		
			
		
	
	
					188 lines
				
				7.4 KiB
			| 
											3 years ago
										 | "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' | ||
|  | }); | ||
|  | class GSSAPI extends auth_provider_1.AuthProvider { | ||
|  |     auth(authContext, callback) { | ||
|  |         const { connection, credentials } = authContext; | ||
|  |         if (credentials == null) | ||
|  |             return callback(new error_1.MongoMissingCredentialsError('Credentials required for GSSAPI authentication')); | ||
|  |         const { username } = credentials; | ||
|  |         function externalCommand(command, cb) { | ||
|  |             return connection.command((0, utils_1.ns)('$external.$cmd'), command, undefined, cb); | ||
|  |         } | ||
|  |         makeKerberosClient(authContext, (err, client) => { | ||
|  |             if (err) | ||
|  |                 return callback(err); | ||
|  |             if (client == null) | ||
|  |                 return callback(new error_1.MongoMissingDependencyError('GSSAPI client missing')); | ||
|  |             client.step('', (err, payload) => { | ||
|  |                 if (err) | ||
|  |                     return callback(err); | ||
|  |                 externalCommand(saslStart(payload), (err, result) => { | ||
|  |                     if (err) | ||
|  |                         return callback(err); | ||
|  |                     if (result == null) | ||
|  |                         return callback(); | ||
|  |                     negotiate(client, 10, result.payload, (err, payload) => { | ||
|  |                         if (err) | ||
|  |                             return callback(err); | ||
|  |                         externalCommand(saslContinue(payload, result.conversationId), (err, result) => { | ||
|  |                             if (err) | ||
|  |                                 return callback(err); | ||
|  |                             if (result == null) | ||
|  |                                 return callback(); | ||
|  |                             finalize(client, username, result.payload, (err, payload) => { | ||
|  |                                 if (err) | ||
|  |                                     return callback(err); | ||
|  |                                 externalCommand({ | ||
|  |                                     saslContinue: 1, | ||
|  |                                     conversationId: result.conversationId, | ||
|  |                                     payload | ||
|  |                                 }, (err, result) => { | ||
|  |                                     if (err) | ||
|  |                                         return callback(err); | ||
|  |                                     callback(undefined, result); | ||
|  |                                 }); | ||
|  |                             }); | ||
|  |                         }); | ||
|  |                     }); | ||
|  |                 }); | ||
|  |             }); | ||
|  |         }); | ||
|  |     } | ||
|  | } | ||
|  | exports.GSSAPI = GSSAPI; | ||
|  | function makeKerberosClient(authContext, callback) { | ||
|  |     const { hostAddress } = authContext.options; | ||
|  |     const { credentials } = authContext; | ||
|  |     if (!hostAddress || typeof hostAddress.host !== 'string' || !credentials) { | ||
|  |         return callback(new error_1.MongoInvalidArgumentError('Connection must have host and port and credentials defined.')); | ||
|  |     } | ||
|  |     if ('kModuleError' in deps_1.Kerberos) { | ||
|  |         return callback(deps_1.Kerberos['kModuleError']); | ||
|  |     } | ||
|  |     const { initializeClient } = deps_1.Kerberos; | ||
|  |     const { username, password } = credentials; | ||
|  |     const mechanismProperties = credentials.mechanismProperties; | ||
|  |     const serviceName = mechanismProperties.SERVICE_NAME ?? 'mongodb'; | ||
|  |     performGSSAPICanonicalizeHostName(hostAddress.host, mechanismProperties, (err, host) => { | ||
|  |         if (err) | ||
|  |             return callback(err); | ||
|  |         const initOptions = {}; | ||
|  |         if (password != null) { | ||
|  |             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}`; | ||
|  |         } | ||
|  |         initializeClient(spn, initOptions, (err, client) => { | ||
|  |             // TODO(NODE-3483)
 | ||
|  |             if (err) | ||
|  |                 return callback(new error_1.MongoRuntimeError(err)); | ||
|  |             callback(undefined, client); | ||
|  |         }); | ||
|  |     }); | ||
|  | } | ||
|  | 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 mode = mechanismProperties.CANONICALIZE_HOST_NAME; | ||
|  |     if (!mode || mode === exports.GSSAPICanonicalizationValue.none) { | ||
|  |         return callback(undefined, host); | ||
|  |     } | ||
|  |     // If forward and reverse or true
 | ||
|  |     if (mode === exports.GSSAPICanonicalizationValue.on || | ||
|  |         mode === exports.GSSAPICanonicalizationValue.forwardAndReverse) { | ||
|  |         // Perform the lookup of the ip address.
 | ||
|  |         dns.lookup(host, (error, address) => { | ||
|  |             // No ip found, return the error.
 | ||
|  |             if (error) | ||
|  |                 return callback(error); | ||
|  |             // Perform a reverse ptr lookup on the ip address.
 | ||
|  |             dns.resolvePtr(address, (err, results) => { | ||
|  |                 // 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.
 | ||
|  |                 if (err) { | ||
|  |                     return resolveCname(host, callback); | ||
|  |                 } | ||
|  |                 // If the ptr did not error but had no results, return the host.
 | ||
|  |                 callback(undefined, results.length > 0 ? results[0] : host); | ||
|  |             }); | ||
|  |         }); | ||
|  |     } | ||
|  |     else { | ||
|  |         // The case for forward is just to resolve the cname as dns.lookup()
 | ||
|  |         // will not return it.
 | ||
|  |         resolveCname(host, callback); | ||
|  |     } | ||
|  | } | ||
|  | exports.performGSSAPICanonicalizeHostName = performGSSAPICanonicalizeHostName; | ||
|  | function resolveCname(host, callback) { | ||
|  |     // Attempt to resolve the host name
 | ||
|  |     dns.resolveCname(host, (err, r) => { | ||
|  |         if (err) | ||
|  |             return callback(undefined, host); | ||
|  |         // Get the first resolve host id
 | ||
|  |         if (r.length > 0) { | ||
|  |             return callback(undefined, r[0]); | ||
|  |         } | ||
|  |         callback(undefined, host); | ||
|  |     }); | ||
|  | } | ||
|  | exports.resolveCname = resolveCname; | ||
|  | //# sourceMappingURL=gssapi.js.map
 |