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
						
					
					
				"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
 |