gssapi.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.resolveCname = exports.performGSSAPICanonicalizeHostName = exports.GSSAPI = exports.GSSAPICanonicalizationValue = void 0;
  4. const dns = require("dns");
  5. const deps_1 = require("../../deps");
  6. const error_1 = require("../../error");
  7. const utils_1 = require("../../utils");
  8. const auth_provider_1 = require("./auth_provider");
  9. /** @public */
  10. exports.GSSAPICanonicalizationValue = Object.freeze({
  11. on: true,
  12. off: false,
  13. none: 'none',
  14. forward: 'forward',
  15. forwardAndReverse: 'forwardAndReverse'
  16. });
  17. async function externalCommand(connection, command) {
  18. return connection.commandAsync((0, utils_1.ns)('$external.$cmd'), command, undefined);
  19. }
  20. let krb;
  21. class GSSAPI extends auth_provider_1.AuthProvider {
  22. async auth(authContext) {
  23. const { connection, credentials } = authContext;
  24. if (credentials == null) {
  25. throw new error_1.MongoMissingCredentialsError('Credentials required for GSSAPI authentication');
  26. }
  27. const { username } = credentials;
  28. const client = await makeKerberosClient(authContext);
  29. const payload = await client.step('');
  30. const saslStartResponse = await externalCommand(connection, saslStart(payload));
  31. const negotiatedPayload = await negotiate(client, 10, saslStartResponse.payload);
  32. const saslContinueResponse = await externalCommand(connection, saslContinue(negotiatedPayload, saslStartResponse.conversationId));
  33. const finalizePayload = await finalize(client, username, saslContinueResponse.payload);
  34. await externalCommand(connection, {
  35. saslContinue: 1,
  36. conversationId: saslContinueResponse.conversationId,
  37. payload: finalizePayload
  38. });
  39. }
  40. }
  41. exports.GSSAPI = GSSAPI;
  42. async function makeKerberosClient(authContext) {
  43. const { hostAddress } = authContext.options;
  44. const { credentials } = authContext;
  45. if (!hostAddress || typeof hostAddress.host !== 'string' || !credentials) {
  46. throw new error_1.MongoInvalidArgumentError('Connection must have host and port and credentials defined.');
  47. }
  48. loadKrb();
  49. if ('kModuleError' in krb) {
  50. throw krb['kModuleError'];
  51. }
  52. const { initializeClient } = krb;
  53. const { username, password } = credentials;
  54. const mechanismProperties = credentials.mechanismProperties;
  55. const serviceName = mechanismProperties.SERVICE_NAME ?? 'mongodb';
  56. const host = await performGSSAPICanonicalizeHostName(hostAddress.host, mechanismProperties);
  57. const initOptions = {};
  58. if (password != null) {
  59. // TODO(NODE-5139): These do not match the typescript options in initializeClient
  60. Object.assign(initOptions, { user: username, password: password });
  61. }
  62. const spnHost = mechanismProperties.SERVICE_HOST ?? host;
  63. let spn = `${serviceName}${process.platform === 'win32' ? '/' : '@'}${spnHost}`;
  64. if ('SERVICE_REALM' in mechanismProperties) {
  65. spn = `${spn}@${mechanismProperties.SERVICE_REALM}`;
  66. }
  67. return initializeClient(spn, initOptions);
  68. }
  69. function saslStart(payload) {
  70. return {
  71. saslStart: 1,
  72. mechanism: 'GSSAPI',
  73. payload,
  74. autoAuthorize: 1
  75. };
  76. }
  77. function saslContinue(payload, conversationId) {
  78. return {
  79. saslContinue: 1,
  80. conversationId,
  81. payload
  82. };
  83. }
  84. async function negotiate(client, retries, payload) {
  85. try {
  86. const response = await client.step(payload);
  87. return response || '';
  88. }
  89. catch (error) {
  90. if (retries === 0) {
  91. // Retries exhausted, raise error
  92. throw error;
  93. }
  94. // Adjust number of retries and call step again
  95. return negotiate(client, retries - 1, payload);
  96. }
  97. }
  98. async function finalize(client, user, payload) {
  99. // GSS Client Unwrap
  100. const response = await client.unwrap(payload);
  101. return client.wrap(response || '', { user });
  102. }
  103. async function performGSSAPICanonicalizeHostName(host, mechanismProperties) {
  104. const mode = mechanismProperties.CANONICALIZE_HOST_NAME;
  105. if (!mode || mode === exports.GSSAPICanonicalizationValue.none) {
  106. return host;
  107. }
  108. // If forward and reverse or true
  109. if (mode === exports.GSSAPICanonicalizationValue.on ||
  110. mode === exports.GSSAPICanonicalizationValue.forwardAndReverse) {
  111. // Perform the lookup of the ip address.
  112. const { address } = await dns.promises.lookup(host);
  113. try {
  114. // Perform a reverse ptr lookup on the ip address.
  115. const results = await dns.promises.resolvePtr(address);
  116. // If the ptr did not error but had no results, return the host.
  117. return results.length > 0 ? results[0] : host;
  118. }
  119. catch (error) {
  120. // This can error as ptr records may not exist for all ips. In this case
  121. // fallback to a cname lookup as dns.lookup() does not return the
  122. // cname.
  123. return resolveCname(host);
  124. }
  125. }
  126. else {
  127. // The case for forward is just to resolve the cname as dns.lookup()
  128. // will not return it.
  129. return resolveCname(host);
  130. }
  131. }
  132. exports.performGSSAPICanonicalizeHostName = performGSSAPICanonicalizeHostName;
  133. async function resolveCname(host) {
  134. // Attempt to resolve the host name
  135. try {
  136. const results = await dns.promises.resolveCname(host);
  137. // Get the first resolved host id
  138. return results.length > 0 ? results[0] : host;
  139. }
  140. catch {
  141. return host;
  142. }
  143. }
  144. exports.resolveCname = resolveCname;
  145. /**
  146. * Load the Kerberos library.
  147. */
  148. function loadKrb() {
  149. if (!krb) {
  150. krb = (0, deps_1.getKerberos)();
  151. }
  152. }
  153. //# sourceMappingURL=gssapi.js.map