| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 | "use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.MongoDBAWS = void 0;const crypto = require("crypto");const process = require("process");const util_1 = require("util");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");/** * The following regions use the global AWS STS endpoint, sts.amazonaws.com, by default * https://docs.aws.amazon.com/sdkref/latest/guide/feature-sts-regionalized-endpoints.html */const LEGACY_REGIONS = new Set([    'ap-northeast-1',    'ap-south-1',    'ap-southeast-1',    'ap-southeast-2',    'aws-global',    'ca-central-1',    'eu-central-1',    'eu-north-1',    'eu-west-1',    'eu-west-2',    'eu-west-3',    'sa-east-1',    'us-east-1',    'us-east-2',    'us-west-1',    'us-west-2']);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 = {    useBigInt64: false,    promoteLongs: true,    promoteValues: true,    promoteBuffers: false,    bsonRegExp: false};class MongoDBAWS extends auth_provider_1.AuthProvider {    constructor() {        super();        this.randomBytesAsync = (0, util_1.promisify)(crypto.randomBytes);    }    async auth(authContext) {        const { connection } = authContext;        if (!authContext.credentials) {            throw new error_1.MongoMissingCredentialsError('AuthContext must provide credentials.');        }        if ('kModuleError' in deps_1.aws4) {            throw deps_1.aws4['kModuleError'];        }        const { sign } = deps_1.aws4;        if ((0, utils_1.maxWireVersion)(connection) < 9) {            throw new error_1.MongoCompatibilityError('MONGODB-AWS authentication requires MongoDB version 4.4 or later');        }        if (!authContext.credentials.username) {            authContext.credentials = await makeTempCredentials(authContext.credentials);        }        const { credentials } = authContext;        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;        const nonce = await this.randomBytesAsync(32);        const saslStart = {            saslStart: 1,            mechanism: 'MONGODB-AWS',            payload: BSON.serialize({ r: nonce, p: ASCII_N }, bsonOptions)        };        const saslStartResponse = await connection.commandAsync((0, utils_1.ns)(`${db}.$cmd`), saslStart, undefined);        const serverResponse = BSON.deserialize(saslStartResponse.payload.buffer, bsonOptions);        const host = serverResponse.h;        const serverNonce = serverResponse.s.buffer;        if (serverNonce.length !== 64) {            // TODO(NODE-3483)            throw new error_1.MongoRuntimeError(`Invalid server nonce length ${serverNonce.length}, expected 64`);        }        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)            throw new error_1.MongoRuntimeError('Server nonce does not begin with client nonce');        }        if (host.length < 1 || host.length > 255 || host.indexOf('..') !== -1) {            // TODO(NODE-3483)            throw new error_1.MongoRuntimeError(`Server returned an invalid host: "${host}"`);        }        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)        };        await connection.commandAsync((0, utils_1.ns)(`${db}.$cmd`), saslContinue, undefined);    }}MongoDBAWS.credentialProvider = null;exports.MongoDBAWS = MongoDBAWS;async function makeTempCredentials(credentials) {    function makeMongoCredentialsFromAWSTemp(creds) {        if (!creds.AccessKeyId || !creds.SecretAccessKey || !creds.Token) {            throw new error_1.MongoMissingCredentialsError('Could not obtain temporary MONGODB-AWS credentials');        }        return 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            }        });    }    MongoDBAWS.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 MongoDBAWS.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) {            return makeMongoCredentialsFromAWSTemp(await (0, utils_1.request)(`${AWS_RELATIVE_URI}${process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}`));        }        // Otherwise assume we are on an EC2 instance        // get a token        const token = await (0, utils_1.request)(`${AWS_EC2_URI}/latest/api/token`, {            method: 'PUT',            json: false,            headers: { 'X-aws-ec2-metadata-token-ttl-seconds': 30 }        });        // get role name        const roleName = await (0, utils_1.request)(`${AWS_EC2_URI}/${AWS_EC2_PATH}`, {            json: false,            headers: { 'X-aws-ec2-metadata-token': token }        });        // get temp credentials        const creds = await (0, utils_1.request)(`${AWS_EC2_URI}/${AWS_EC2_PATH}/${roleName}`, {            headers: { 'X-aws-ec2-metadata-token': token }        });        return makeMongoCredentialsFromAWSTemp(creds);    }    else {        let { AWS_STS_REGIONAL_ENDPOINTS = '', AWS_REGION = '' } = process.env;        AWS_STS_REGIONAL_ENDPOINTS = AWS_STS_REGIONAL_ENDPOINTS.toLowerCase();        AWS_REGION = AWS_REGION.toLowerCase();        /** The option setting should work only for users who have explicit settings in their environment, the driver should not encode "defaults" */        const awsRegionSettingsExist = AWS_REGION.length !== 0 && AWS_STS_REGIONAL_ENDPOINTS.length !== 0;        /**         * If AWS_STS_REGIONAL_ENDPOINTS is set to regional, users are opting into the new behavior of respecting the region settings         *         * If AWS_STS_REGIONAL_ENDPOINTS is set to legacy, then "old" regions need to keep using the global setting.         * Technically the SDK gets this wrong, it reaches out to 'sts.us-east-1.amazonaws.com' when it should be 'sts.amazonaws.com'.         * That is not our bug to fix here. We leave that up to the SDK.         */        const useRegionalSts = AWS_STS_REGIONAL_ENDPOINTS === 'regional' ||            (AWS_STS_REGIONAL_ENDPOINTS === 'legacy' && !LEGACY_REGIONS.has(AWS_REGION));        const provider = awsRegionSettingsExist && useRegionalSts            ? MongoDBAWS.credentialProvider.fromNodeProviderChain({                clientConfig: { region: AWS_REGION }            })            : MongoDBAWS.credentialProvider.fromNodeProviderChain();        /*         * 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         */        try {            const creds = await provider();            return makeMongoCredentialsFromAWSTemp({                AccessKeyId: creds.accessKeyId,                SecretAccessKey: creds.secretAccessKey,                Token: creds.sessionToken,                Expiration: creds.expiration            });        }        catch (error) {            throw 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];}//# sourceMappingURL=mongodb_aws.js.map
 |