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.
		
		
		
		
		
			
		
			
				
					237 lines
				
				9.8 KiB
			
		
		
			
		
	
	
					237 lines
				
				9.8 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								"use strict";
							 | 
						||
| 
								 | 
							
								Object.defineProperty(exports, "__esModule", { value: true });
							 | 
						||
| 
								 | 
							
								exports.MongoDBAWS = void 0;
							 | 
						||
| 
								 | 
							
								const crypto = require("crypto");
							 | 
						||
| 
								 | 
							
								const http = require("http");
							 | 
						||
| 
								 | 
							
								const url = require("url");
							 | 
						||
| 
								 | 
							
								const BSON = require("../../bson");
							 | 
						||
| 
								 | 
							
								const deps_1 = require("../../deps");
							 | 
						||
| 
								 | 
							
								const error_1 = require("../../error");
							 | 
						||
| 
								 | 
							
								const utils_1 = require("../../utils");
							 | 
						||
| 
								 | 
							
								const auth_provider_1 = require("./auth_provider");
							 | 
						||
| 
								 | 
							
								const mongo_credentials_1 = require("./mongo_credentials");
							 | 
						||
| 
								 | 
							
								const providers_1 = require("./providers");
							 | 
						||
| 
								 | 
							
								const ASCII_N = 110;
							 | 
						||
| 
								 | 
							
								const AWS_RELATIVE_URI = 'http://169.254.170.2';
							 | 
						||
| 
								 | 
							
								const AWS_EC2_URI = 'http://169.254.169.254';
							 | 
						||
| 
								 | 
							
								const AWS_EC2_PATH = '/latest/meta-data/iam/security-credentials';
							 | 
						||
| 
								 | 
							
								const bsonOptions = {
							 | 
						||
| 
								 | 
							
								    promoteLongs: true,
							 | 
						||
| 
								 | 
							
								    promoteValues: true,
							 | 
						||
| 
								 | 
							
								    promoteBuffers: false,
							 | 
						||
| 
								 | 
							
								    bsonRegExp: false
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								class MongoDBAWS extends auth_provider_1.AuthProvider {
							 | 
						||
| 
								 | 
							
								    auth(authContext, callback) {
							 | 
						||
| 
								 | 
							
								        const { connection, credentials } = authContext;
							 | 
						||
| 
								 | 
							
								        if (!credentials) {
							 | 
						||
| 
								 | 
							
								            return callback(new error_1.MongoMissingCredentialsError('AuthContext must provide credentials.'));
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if ('kModuleError' in deps_1.aws4) {
							 | 
						||
| 
								 | 
							
								            return callback(deps_1.aws4['kModuleError']);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        const { sign } = deps_1.aws4;
							 | 
						||
| 
								 | 
							
								        if ((0, utils_1.maxWireVersion)(connection) < 9) {
							 | 
						||
| 
								 | 
							
								            callback(new error_1.MongoCompatibilityError('MONGODB-AWS authentication requires MongoDB version 4.4 or later'));
							 | 
						||
| 
								 | 
							
								            return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if (!credentials.username) {
							 | 
						||
| 
								 | 
							
								            makeTempCredentials(credentials, (err, tempCredentials) => {
							 | 
						||
| 
								 | 
							
								                if (err || !tempCredentials)
							 | 
						||
| 
								 | 
							
								                    return callback(err);
							 | 
						||
| 
								 | 
							
								                authContext.credentials = tempCredentials;
							 | 
						||
| 
								 | 
							
								                this.auth(authContext, callback);
							 | 
						||
| 
								 | 
							
								            });
							 | 
						||
| 
								 | 
							
								            return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        const accessKeyId = credentials.username;
							 | 
						||
| 
								 | 
							
								        const secretAccessKey = credentials.password;
							 | 
						||
| 
								 | 
							
								        const sessionToken = credentials.mechanismProperties.AWS_SESSION_TOKEN;
							 | 
						||
| 
								 | 
							
								        // If all three defined, include sessionToken, else include username and pass, else no credentials
							 | 
						||
| 
								 | 
							
								        const awsCredentials = accessKeyId && secretAccessKey && sessionToken
							 | 
						||
| 
								 | 
							
								            ? { accessKeyId, secretAccessKey, sessionToken }
							 | 
						||
| 
								 | 
							
								            : accessKeyId && secretAccessKey
							 | 
						||
| 
								 | 
							
								                ? { accessKeyId, secretAccessKey }
							 | 
						||
| 
								 | 
							
								                : undefined;
							 | 
						||
| 
								 | 
							
								        const db = credentials.source;
							 | 
						||
| 
								 | 
							
								        crypto.randomBytes(32, (err, nonce) => {
							 | 
						||
| 
								 | 
							
								            if (err) {
							 | 
						||
| 
								 | 
							
								                callback(err);
							 | 
						||
| 
								 | 
							
								                return;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            const saslStart = {
							 | 
						||
| 
								 | 
							
								                saslStart: 1,
							 | 
						||
| 
								 | 
							
								                mechanism: 'MONGODB-AWS',
							 | 
						||
| 
								 | 
							
								                payload: BSON.serialize({ r: nonce, p: ASCII_N }, bsonOptions)
							 | 
						||
| 
								 | 
							
								            };
							 | 
						||
| 
								 | 
							
								            connection.command((0, utils_1.ns)(`${db}.$cmd`), saslStart, undefined, (err, res) => {
							 | 
						||
| 
								 | 
							
								                if (err)
							 | 
						||
| 
								 | 
							
								                    return callback(err);
							 | 
						||
| 
								 | 
							
								                const serverResponse = BSON.deserialize(res.payload.buffer, bsonOptions);
							 | 
						||
| 
								 | 
							
								                const host = serverResponse.h;
							 | 
						||
| 
								 | 
							
								                const serverNonce = serverResponse.s.buffer;
							 | 
						||
| 
								 | 
							
								                if (serverNonce.length !== 64) {
							 | 
						||
| 
								 | 
							
								                    callback(
							 | 
						||
| 
								 | 
							
								                    // TODO(NODE-3483)
							 | 
						||
| 
								 | 
							
								                    new error_1.MongoRuntimeError(`Invalid server nonce length ${serverNonce.length}, expected 64`));
							 | 
						||
| 
								 | 
							
								                    return;
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								                if (!utils_1.ByteUtils.equals(serverNonce.subarray(0, nonce.byteLength), nonce)) {
							 | 
						||
| 
								 | 
							
								                    // throw because the serverNonce's leading 32 bytes must equal the client nonce's 32 bytes
							 | 
						||
| 
								 | 
							
								                    // https://github.com/mongodb/specifications/blob/875446db44aade414011731840831f38a6c668df/source/auth/auth.rst#id11
							 | 
						||
| 
								 | 
							
								                    // TODO(NODE-3483)
							 | 
						||
| 
								 | 
							
								                    callback(new error_1.MongoRuntimeError('Server nonce does not begin with client nonce'));
							 | 
						||
| 
								 | 
							
								                    return;
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								                if (host.length < 1 || host.length > 255 || host.indexOf('..') !== -1) {
							 | 
						||
| 
								 | 
							
								                    // TODO(NODE-3483)
							 | 
						||
| 
								 | 
							
								                    callback(new error_1.MongoRuntimeError(`Server returned an invalid host: "${host}"`));
							 | 
						||
| 
								 | 
							
								                    return;
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								                const body = 'Action=GetCallerIdentity&Version=2011-06-15';
							 | 
						||
| 
								 | 
							
								                const options = sign({
							 | 
						||
| 
								 | 
							
								                    method: 'POST',
							 | 
						||
| 
								 | 
							
								                    host,
							 | 
						||
| 
								 | 
							
								                    region: deriveRegion(serverResponse.h),
							 | 
						||
| 
								 | 
							
								                    service: 'sts',
							 | 
						||
| 
								 | 
							
								                    headers: {
							 | 
						||
| 
								 | 
							
								                        'Content-Type': 'application/x-www-form-urlencoded',
							 | 
						||
| 
								 | 
							
								                        'Content-Length': body.length,
							 | 
						||
| 
								 | 
							
								                        'X-MongoDB-Server-Nonce': utils_1.ByteUtils.toBase64(serverNonce),
							 | 
						||
| 
								 | 
							
								                        'X-MongoDB-GS2-CB-Flag': 'n'
							 | 
						||
| 
								 | 
							
								                    },
							 | 
						||
| 
								 | 
							
								                    path: '/',
							 | 
						||
| 
								 | 
							
								                    body
							 | 
						||
| 
								 | 
							
								                }, awsCredentials);
							 | 
						||
| 
								 | 
							
								                const payload = {
							 | 
						||
| 
								 | 
							
								                    a: options.headers.Authorization,
							 | 
						||
| 
								 | 
							
								                    d: options.headers['X-Amz-Date']
							 | 
						||
| 
								 | 
							
								                };
							 | 
						||
| 
								 | 
							
								                if (sessionToken) {
							 | 
						||
| 
								 | 
							
								                    payload.t = sessionToken;
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								                const saslContinue = {
							 | 
						||
| 
								 | 
							
								                    saslContinue: 1,
							 | 
						||
| 
								 | 
							
								                    conversationId: 1,
							 | 
						||
| 
								 | 
							
								                    payload: BSON.serialize(payload, bsonOptions)
							 | 
						||
| 
								 | 
							
								                };
							 | 
						||
| 
								 | 
							
								                connection.command((0, utils_1.ns)(`${db}.$cmd`), saslContinue, undefined, callback);
							 | 
						||
| 
								 | 
							
								            });
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								exports.MongoDBAWS = MongoDBAWS;
							 | 
						||
| 
								 | 
							
								function makeTempCredentials(credentials, callback) {
							 | 
						||
| 
								 | 
							
								    function done(creds) {
							 | 
						||
| 
								 | 
							
								        if (!creds.AccessKeyId || !creds.SecretAccessKey || !creds.Token) {
							 | 
						||
| 
								 | 
							
								            callback(new error_1.MongoMissingCredentialsError('Could not obtain temporary MONGODB-AWS credentials'));
							 | 
						||
| 
								 | 
							
								            return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        callback(undefined, new mongo_credentials_1.MongoCredentials({
							 | 
						||
| 
								 | 
							
								            username: creds.AccessKeyId,
							 | 
						||
| 
								 | 
							
								            password: creds.SecretAccessKey,
							 | 
						||
| 
								 | 
							
								            source: credentials.source,
							 | 
						||
| 
								 | 
							
								            mechanism: providers_1.AuthMechanism.MONGODB_AWS,
							 | 
						||
| 
								 | 
							
								            mechanismProperties: {
							 | 
						||
| 
								 | 
							
								                AWS_SESSION_TOKEN: creds.Token
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }));
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    const credentialProvider = (0, deps_1.getAwsCredentialProvider)();
							 | 
						||
| 
								 | 
							
								    // Check if the AWS credential provider from the SDK is present. If not,
							 | 
						||
| 
								 | 
							
								    // use the old method.
							 | 
						||
| 
								 | 
							
								    if ('kModuleError' in credentialProvider) {
							 | 
						||
| 
								 | 
							
								        // If the environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
							 | 
						||
| 
								 | 
							
								        // is set then drivers MUST assume that it was set by an AWS ECS agent
							 | 
						||
| 
								 | 
							
								        if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {
							 | 
						||
| 
								 | 
							
								            request(`${AWS_RELATIVE_URI}${process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}`, undefined, (err, res) => {
							 | 
						||
| 
								 | 
							
								                if (err)
							 | 
						||
| 
								 | 
							
								                    return callback(err);
							 | 
						||
| 
								 | 
							
								                done(res);
							 | 
						||
| 
								 | 
							
								            });
							 | 
						||
| 
								 | 
							
								            return;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        // Otherwise assume we are on an EC2 instance
							 | 
						||
| 
								 | 
							
								        // get a token
							 | 
						||
| 
								 | 
							
								        request(`${AWS_EC2_URI}/latest/api/token`, { method: 'PUT', json: false, headers: { 'X-aws-ec2-metadata-token-ttl-seconds': 30 } }, (err, token) => {
							 | 
						||
| 
								 | 
							
								            if (err)
							 | 
						||
| 
								 | 
							
								                return callback(err);
							 | 
						||
| 
								 | 
							
								            // get role name
							 | 
						||
| 
								 | 
							
								            request(`${AWS_EC2_URI}/${AWS_EC2_PATH}`, { json: false, headers: { 'X-aws-ec2-metadata-token': token } }, (err, roleName) => {
							 | 
						||
| 
								 | 
							
								                if (err)
							 | 
						||
| 
								 | 
							
								                    return callback(err);
							 | 
						||
| 
								 | 
							
								                // get temp credentials
							 | 
						||
| 
								 | 
							
								                request(`${AWS_EC2_URI}/${AWS_EC2_PATH}/${roleName}`, { headers: { 'X-aws-ec2-metadata-token': token } }, (err, creds) => {
							 | 
						||
| 
								 | 
							
								                    if (err)
							 | 
						||
| 
								 | 
							
								                        return callback(err);
							 | 
						||
| 
								 | 
							
								                    done(creds);
							 | 
						||
| 
								 | 
							
								                });
							 | 
						||
| 
								 | 
							
								            });
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    else {
							 | 
						||
| 
								 | 
							
								        /*
							 | 
						||
| 
								 | 
							
								         * Creates a credential provider that will attempt to find credentials from the
							 | 
						||
| 
								 | 
							
								         * following sources (listed in order of precedence):
							 | 
						||
| 
								 | 
							
								         *
							 | 
						||
| 
								 | 
							
								         * - Environment variables exposed via process.env
							 | 
						||
| 
								 | 
							
								         * - SSO credentials from token cache
							 | 
						||
| 
								 | 
							
								         * - Web identity token credentials
							 | 
						||
| 
								 | 
							
								         * - Shared credentials and config ini files
							 | 
						||
| 
								 | 
							
								         * - The EC2/ECS Instance Metadata Service
							 | 
						||
| 
								 | 
							
								         */
							 | 
						||
| 
								 | 
							
								        const { fromNodeProviderChain } = credentialProvider;
							 | 
						||
| 
								 | 
							
								        const provider = fromNodeProviderChain();
							 | 
						||
| 
								 | 
							
								        provider()
							 | 
						||
| 
								 | 
							
								            .then((creds) => {
							 | 
						||
| 
								 | 
							
								            done({
							 | 
						||
| 
								 | 
							
								                AccessKeyId: creds.accessKeyId,
							 | 
						||
| 
								 | 
							
								                SecretAccessKey: creds.secretAccessKey,
							 | 
						||
| 
								 | 
							
								                Token: creds.sessionToken,
							 | 
						||
| 
								 | 
							
								                Expiration: creds.expiration
							 | 
						||
| 
								 | 
							
								            });
							 | 
						||
| 
								 | 
							
								        })
							 | 
						||
| 
								 | 
							
								            .catch((error) => {
							 | 
						||
| 
								 | 
							
								            callback(new error_1.MongoAWSError(error.message));
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								function deriveRegion(host) {
							 | 
						||
| 
								 | 
							
								    const parts = host.split('.');
							 | 
						||
| 
								 | 
							
								    if (parts.length === 1 || parts[1] === 'amazonaws') {
							 | 
						||
| 
								 | 
							
								        return 'us-east-1';
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    return parts[1];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								function request(uri, _options, callback) {
							 | 
						||
| 
								 | 
							
								    const options = Object.assign({
							 | 
						||
| 
								 | 
							
								        method: 'GET',
							 | 
						||
| 
								 | 
							
								        timeout: 10000,
							 | 
						||
| 
								 | 
							
								        json: true
							 | 
						||
| 
								 | 
							
								    }, url.parse(uri), _options);
							 | 
						||
| 
								 | 
							
								    const req = http.request(options, res => {
							 | 
						||
| 
								 | 
							
								        res.setEncoding('utf8');
							 | 
						||
| 
								 | 
							
								        let data = '';
							 | 
						||
| 
								 | 
							
								        res.on('data', d => (data += d));
							 | 
						||
| 
								 | 
							
								        res.on('end', () => {
							 | 
						||
| 
								 | 
							
								            if (options.json === false) {
							 | 
						||
| 
								 | 
							
								                callback(undefined, data);
							 | 
						||
| 
								 | 
							
								                return;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            try {
							 | 
						||
| 
								 | 
							
								                const parsed = JSON.parse(data);
							 | 
						||
| 
								 | 
							
								                callback(undefined, parsed);
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            catch (err) {
							 | 
						||
| 
								 | 
							
								                // TODO(NODE-3483)
							 | 
						||
| 
								 | 
							
								                callback(new error_1.MongoRuntimeError(`Invalid JSON response: "${data}"`));
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								    req.on('timeout', () => {
							 | 
						||
| 
								 | 
							
								        req.destroy(new error_1.MongoAWSError(`AWS request to ${uri} timed out after ${options.timeout} ms`));
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								    req.on('error', err => callback(err));
							 | 
						||
| 
								 | 
							
								    req.end();
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								//# sourceMappingURL=mongodb_aws.js.map
							 |