mongodb_aws.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.MongoDBAWS = void 0;
  4. const crypto = require("crypto");
  5. const process = require("process");
  6. const util_1 = require("util");
  7. const BSON = require("../../bson");
  8. const deps_1 = require("../../deps");
  9. const error_1 = require("../../error");
  10. const utils_1 = require("../../utils");
  11. const auth_provider_1 = require("./auth_provider");
  12. const mongo_credentials_1 = require("./mongo_credentials");
  13. const providers_1 = require("./providers");
  14. /**
  15. * The following regions use the global AWS STS endpoint, sts.amazonaws.com, by default
  16. * https://docs.aws.amazon.com/sdkref/latest/guide/feature-sts-regionalized-endpoints.html
  17. */
  18. const LEGACY_REGIONS = new Set([
  19. 'ap-northeast-1',
  20. 'ap-south-1',
  21. 'ap-southeast-1',
  22. 'ap-southeast-2',
  23. 'aws-global',
  24. 'ca-central-1',
  25. 'eu-central-1',
  26. 'eu-north-1',
  27. 'eu-west-1',
  28. 'eu-west-2',
  29. 'eu-west-3',
  30. 'sa-east-1',
  31. 'us-east-1',
  32. 'us-east-2',
  33. 'us-west-1',
  34. 'us-west-2'
  35. ]);
  36. const ASCII_N = 110;
  37. const AWS_RELATIVE_URI = 'http://169.254.170.2';
  38. const AWS_EC2_URI = 'http://169.254.169.254';
  39. const AWS_EC2_PATH = '/latest/meta-data/iam/security-credentials';
  40. const bsonOptions = {
  41. useBigInt64: false,
  42. promoteLongs: true,
  43. promoteValues: true,
  44. promoteBuffers: false,
  45. bsonRegExp: false
  46. };
  47. class MongoDBAWS extends auth_provider_1.AuthProvider {
  48. constructor() {
  49. super();
  50. this.randomBytesAsync = (0, util_1.promisify)(crypto.randomBytes);
  51. }
  52. async auth(authContext) {
  53. const { connection } = authContext;
  54. if (!authContext.credentials) {
  55. throw new error_1.MongoMissingCredentialsError('AuthContext must provide credentials.');
  56. }
  57. if ('kModuleError' in deps_1.aws4) {
  58. throw deps_1.aws4['kModuleError'];
  59. }
  60. const { sign } = deps_1.aws4;
  61. if ((0, utils_1.maxWireVersion)(connection) < 9) {
  62. throw new error_1.MongoCompatibilityError('MONGODB-AWS authentication requires MongoDB version 4.4 or later');
  63. }
  64. if (!authContext.credentials.username) {
  65. authContext.credentials = await makeTempCredentials(authContext.credentials);
  66. }
  67. const { credentials } = authContext;
  68. const accessKeyId = credentials.username;
  69. const secretAccessKey = credentials.password;
  70. const sessionToken = credentials.mechanismProperties.AWS_SESSION_TOKEN;
  71. // If all three defined, include sessionToken, else include username and pass, else no credentials
  72. const awsCredentials = accessKeyId && secretAccessKey && sessionToken
  73. ? { accessKeyId, secretAccessKey, sessionToken }
  74. : accessKeyId && secretAccessKey
  75. ? { accessKeyId, secretAccessKey }
  76. : undefined;
  77. const db = credentials.source;
  78. const nonce = await this.randomBytesAsync(32);
  79. const saslStart = {
  80. saslStart: 1,
  81. mechanism: 'MONGODB-AWS',
  82. payload: BSON.serialize({ r: nonce, p: ASCII_N }, bsonOptions)
  83. };
  84. const saslStartResponse = await connection.commandAsync((0, utils_1.ns)(`${db}.$cmd`), saslStart, undefined);
  85. const serverResponse = BSON.deserialize(saslStartResponse.payload.buffer, bsonOptions);
  86. const host = serverResponse.h;
  87. const serverNonce = serverResponse.s.buffer;
  88. if (serverNonce.length !== 64) {
  89. // TODO(NODE-3483)
  90. throw new error_1.MongoRuntimeError(`Invalid server nonce length ${serverNonce.length}, expected 64`);
  91. }
  92. if (!utils_1.ByteUtils.equals(serverNonce.subarray(0, nonce.byteLength), nonce)) {
  93. // throw because the serverNonce's leading 32 bytes must equal the client nonce's 32 bytes
  94. // https://github.com/mongodb/specifications/blob/875446db44aade414011731840831f38a6c668df/source/auth/auth.rst#id11
  95. // TODO(NODE-3483)
  96. throw new error_1.MongoRuntimeError('Server nonce does not begin with client nonce');
  97. }
  98. if (host.length < 1 || host.length > 255 || host.indexOf('..') !== -1) {
  99. // TODO(NODE-3483)
  100. throw new error_1.MongoRuntimeError(`Server returned an invalid host: "${host}"`);
  101. }
  102. const body = 'Action=GetCallerIdentity&Version=2011-06-15';
  103. const options = sign({
  104. method: 'POST',
  105. host,
  106. region: deriveRegion(serverResponse.h),
  107. service: 'sts',
  108. headers: {
  109. 'Content-Type': 'application/x-www-form-urlencoded',
  110. 'Content-Length': body.length,
  111. 'X-MongoDB-Server-Nonce': utils_1.ByteUtils.toBase64(serverNonce),
  112. 'X-MongoDB-GS2-CB-Flag': 'n'
  113. },
  114. path: '/',
  115. body
  116. }, awsCredentials);
  117. const payload = {
  118. a: options.headers.Authorization,
  119. d: options.headers['X-Amz-Date']
  120. };
  121. if (sessionToken) {
  122. payload.t = sessionToken;
  123. }
  124. const saslContinue = {
  125. saslContinue: 1,
  126. conversationId: 1,
  127. payload: BSON.serialize(payload, bsonOptions)
  128. };
  129. await connection.commandAsync((0, utils_1.ns)(`${db}.$cmd`), saslContinue, undefined);
  130. }
  131. }
  132. MongoDBAWS.credentialProvider = null;
  133. exports.MongoDBAWS = MongoDBAWS;
  134. async function makeTempCredentials(credentials) {
  135. function makeMongoCredentialsFromAWSTemp(creds) {
  136. if (!creds.AccessKeyId || !creds.SecretAccessKey || !creds.Token) {
  137. throw new error_1.MongoMissingCredentialsError('Could not obtain temporary MONGODB-AWS credentials');
  138. }
  139. return new mongo_credentials_1.MongoCredentials({
  140. username: creds.AccessKeyId,
  141. password: creds.SecretAccessKey,
  142. source: credentials.source,
  143. mechanism: providers_1.AuthMechanism.MONGODB_AWS,
  144. mechanismProperties: {
  145. AWS_SESSION_TOKEN: creds.Token
  146. }
  147. });
  148. }
  149. MongoDBAWS.credentialProvider ?? (MongoDBAWS.credentialProvider = (0, deps_1.getAwsCredentialProvider)());
  150. // Check if the AWS credential provider from the SDK is present. If not,
  151. // use the old method.
  152. if ('kModuleError' in MongoDBAWS.credentialProvider) {
  153. // If the environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
  154. // is set then drivers MUST assume that it was set by an AWS ECS agent
  155. if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {
  156. return makeMongoCredentialsFromAWSTemp(await (0, utils_1.request)(`${AWS_RELATIVE_URI}${process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}`));
  157. }
  158. // Otherwise assume we are on an EC2 instance
  159. // get a token
  160. const token = await (0, utils_1.request)(`${AWS_EC2_URI}/latest/api/token`, {
  161. method: 'PUT',
  162. json: false,
  163. headers: { 'X-aws-ec2-metadata-token-ttl-seconds': 30 }
  164. });
  165. // get role name
  166. const roleName = await (0, utils_1.request)(`${AWS_EC2_URI}/${AWS_EC2_PATH}`, {
  167. json: false,
  168. headers: { 'X-aws-ec2-metadata-token': token }
  169. });
  170. // get temp credentials
  171. const creds = await (0, utils_1.request)(`${AWS_EC2_URI}/${AWS_EC2_PATH}/${roleName}`, {
  172. headers: { 'X-aws-ec2-metadata-token': token }
  173. });
  174. return makeMongoCredentialsFromAWSTemp(creds);
  175. }
  176. else {
  177. let { AWS_STS_REGIONAL_ENDPOINTS = '', AWS_REGION = '' } = process.env;
  178. AWS_STS_REGIONAL_ENDPOINTS = AWS_STS_REGIONAL_ENDPOINTS.toLowerCase();
  179. AWS_REGION = AWS_REGION.toLowerCase();
  180. /** The option setting should work only for users who have explicit settings in their environment, the driver should not encode "defaults" */
  181. const awsRegionSettingsExist = AWS_REGION.length !== 0 && AWS_STS_REGIONAL_ENDPOINTS.length !== 0;
  182. /**
  183. * If AWS_STS_REGIONAL_ENDPOINTS is set to regional, users are opting into the new behavior of respecting the region settings
  184. *
  185. * If AWS_STS_REGIONAL_ENDPOINTS is set to legacy, then "old" regions need to keep using the global setting.
  186. * Technically the SDK gets this wrong, it reaches out to 'sts.us-east-1.amazonaws.com' when it should be 'sts.amazonaws.com'.
  187. * That is not our bug to fix here. We leave that up to the SDK.
  188. */
  189. const useRegionalSts = AWS_STS_REGIONAL_ENDPOINTS === 'regional' ||
  190. (AWS_STS_REGIONAL_ENDPOINTS === 'legacy' && !LEGACY_REGIONS.has(AWS_REGION));
  191. const provider = awsRegionSettingsExist && useRegionalSts
  192. ? MongoDBAWS.credentialProvider.fromNodeProviderChain({
  193. clientConfig: { region: AWS_REGION }
  194. })
  195. : MongoDBAWS.credentialProvider.fromNodeProviderChain();
  196. /*
  197. * Creates a credential provider that will attempt to find credentials from the
  198. * following sources (listed in order of precedence):
  199. *
  200. * - Environment variables exposed via process.env
  201. * - SSO credentials from token cache
  202. * - Web identity token credentials
  203. * - Shared credentials and config ini files
  204. * - The EC2/ECS Instance Metadata Service
  205. */
  206. try {
  207. const creds = await provider();
  208. return makeMongoCredentialsFromAWSTemp({
  209. AccessKeyId: creds.accessKeyId,
  210. SecretAccessKey: creds.secretAccessKey,
  211. Token: creds.sessionToken,
  212. Expiration: creds.expiration
  213. });
  214. }
  215. catch (error) {
  216. throw new error_1.MongoAWSError(error.message);
  217. }
  218. }
  219. }
  220. function deriveRegion(host) {
  221. const parts = host.split('.');
  222. if (parts.length === 1 || parts[1] === 'amazonaws') {
  223. return 'us-east-1';
  224. }
  225. return parts[1];
  226. }
  227. //# sourceMappingURL=mongodb_aws.js.map