gssapi.js 5.6 KB

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