File size: 6,238 Bytes
9756872
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MongoDBAWS = void 0;
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 aws_temporary_credentials_1 = require("./aws_temporary_credentials");
const mongo_credentials_1 = require("./mongo_credentials");
const providers_1 = require("./providers");
const ASCII_N = 110;
const bsonOptions = {
    useBigInt64: false,
    promoteLongs: true,
    promoteValues: true,
    promoteBuffers: false,
    bsonRegExp: false
};
class MongoDBAWS extends auth_provider_1.AuthProvider {
    constructor() {
        super();
        this.credentialFetcher = aws_temporary_credentials_1.AWSTemporaryCredentialProvider.isAWSSDKInstalled
            ? new aws_temporary_credentials_1.AWSSDKCredentialProvider()
            : new aws_temporary_credentials_1.LegacyAWSTemporaryCredentialProvider();
    }
    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, this.credentialFetcher);
        }
        const { credentials } = authContext;
        const accessKeyId = credentials.username;
        const secretAccessKey = credentials.password;
        // Allow the user to specify an AWS session token for authentication with temporary credentials.
        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 (0, utils_1.randomBytes)(32);
        // All messages between MongoDB clients and servers are sent as BSON objects
        // in the payload field of saslStart and saslContinue.
        const saslStart = {
            saslStart: 1,
            mechanism: 'MONGODB-AWS',
            payload: BSON.serialize({ r: nonce, p: ASCII_N }, bsonOptions)
        };
        const saslStartResponse = await connection.command((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.command((0, utils_1.ns)(`${db}.$cmd`), saslContinue, undefined);
    }
}
exports.MongoDBAWS = MongoDBAWS;
async function makeTempCredentials(credentials, awsCredentialFetcher) {
    function makeMongoCredentialsFromAWSTemp(creds) {
        // The AWS session token (creds.Token) may or may not be set.
        if (!creds.AccessKeyId || !creds.SecretAccessKey) {
            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
            }
        });
    }
    const temporaryCredentials = await awsCredentialFetcher.getCredentials();
    return makeMongoCredentialsFromAWSTemp(temporaryCredentials);
}
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