mongodb 7.5.0 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/lib/bulk/common.js +27 -5
- package/lib/bulk/common.js.map +1 -1
- package/lib/bulk/ordered.js +25 -7
- package/lib/bulk/ordered.js.map +1 -1
- package/lib/bulk/unordered.js +25 -7
- package/lib/bulk/unordered.js.map +1 -1
- package/lib/client-side-encryption/auto_encrypter.js +8 -2
- package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
- package/lib/client-side-encryption/client_encryption.js +12 -4
- package/lib/client-side-encryption/client_encryption.js.map +1 -1
- package/lib/client-side-encryption/kms_options.js +3 -0
- package/lib/client-side-encryption/kms_options.js.map +1 -0
- package/lib/client-side-encryption/state_machine.js +54 -7
- package/lib/client-side-encryption/state_machine.js.map +1 -1
- package/lib/cmap/auth/gssapi.js +2 -1
- package/lib/cmap/auth/gssapi.js.map +1 -1
- package/lib/cmap/auth/mongo_credentials.js +0 -1
- package/lib/cmap/auth/mongo_credentials.js.map +1 -1
- package/lib/cmap/auth/mongodb_aws.js +0 -3
- package/lib/cmap/auth/mongodb_aws.js.map +1 -1
- package/lib/cmap/command_monitoring_events.js +6 -7
- package/lib/cmap/command_monitoring_events.js.map +1 -1
- package/lib/cmap/commands.js +13 -0
- package/lib/cmap/commands.js.map +1 -1
- package/lib/cmap/connect.js +2 -2
- package/lib/cmap/connect.js.map +1 -1
- package/lib/cmap/connection.js +3 -1
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/handshake/client_metadata.js +2 -1
- package/lib/cmap/handshake/client_metadata.js.map +1 -1
- package/lib/cmap/wire_protocol/constants.js +2 -2
- package/lib/connection_string.js +4 -0
- package/lib/connection_string.js.map +1 -1
- package/lib/cursor/abstract_cursor.js.map +1 -1
- package/lib/error.js +7 -11
- package/lib/error.js.map +1 -1
- package/lib/gridfs/download.js +1 -1
- package/lib/gridfs/download.js.map +1 -1
- package/lib/index.js.map +1 -1
- package/lib/operations/command.js.map +1 -1
- package/lib/operations/delete.js +7 -10
- package/lib/operations/delete.js.map +1 -1
- package/lib/operations/execute_operation.js +29 -2
- package/lib/operations/execute_operation.js.map +1 -1
- package/lib/operations/find_and_modify.js +1 -5
- package/lib/operations/find_and_modify.js.map +1 -1
- package/lib/operations/get_more.js +2 -3
- package/lib/operations/get_more.js.map +1 -1
- package/lib/operations/indexes.js +3 -9
- package/lib/operations/indexes.js.map +1 -1
- package/lib/operations/insert.js +6 -2
- package/lib/operations/insert.js.map +1 -1
- package/lib/operations/list_collections.js +2 -3
- package/lib/operations/list_collections.js.map +1 -1
- package/lib/operations/list_databases.js +2 -2
- package/lib/operations/list_databases.js.map +1 -1
- package/lib/operations/update.js +6 -2
- package/lib/operations/update.js.map +1 -1
- package/lib/runtime_adapters.js +15 -16
- package/lib/runtime_adapters.js.map +1 -1
- package/lib/sdam/server.js +1 -1
- package/lib/sdam/server.js.map +1 -1
- package/lib/sessions.js +2 -2
- package/lib/sessions.js.map +1 -1
- package/lib/utils.js +1 -3
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +77 -20
- package/package.json +3 -2
- package/src/bulk/common.ts +37 -5
- package/src/bulk/ordered.ts +23 -7
- package/src/bulk/unordered.ts +23 -8
- package/src/client-side-encryption/auto_encrypter.ts +19 -3
- package/src/client-side-encryption/client_encryption.ts +29 -9
- package/src/client-side-encryption/kms_options.ts +93 -0
- package/src/client-side-encryption/state_machine.ts +68 -47
- package/src/cmap/auth/gssapi.ts +3 -4
- package/src/cmap/auth/mongo_credentials.ts +0 -1
- package/src/cmap/auth/mongodb_aws.ts +2 -12
- package/src/cmap/command_monitoring_events.ts +9 -10
- package/src/cmap/commands.ts +17 -0
- package/src/cmap/connect.ts +3 -3
- package/src/cmap/connection.ts +4 -2
- package/src/cmap/handshake/client_metadata.ts +2 -1
- package/src/cmap/wire_protocol/constants.ts +2 -2
- package/src/connection_string.ts +4 -0
- package/src/cursor/abstract_cursor.ts +0 -5
- package/src/error.ts +7 -19
- package/src/gridfs/download.ts +1 -1
- package/src/index.ts +7 -6
- package/src/mongo_client.ts +1 -1
- package/src/operations/command.ts +0 -5
- package/src/operations/delete.ts +17 -15
- package/src/operations/execute_operation.ts +29 -2
- package/src/operations/find_and_modify.ts +3 -15
- package/src/operations/get_more.ts +3 -5
- package/src/operations/indexes.ts +6 -16
- package/src/operations/insert.ts +13 -2
- package/src/operations/list_collections.ts +5 -6
- package/src/operations/list_databases.ts +3 -3
- package/src/operations/update.ts +9 -2
- package/src/operations/validate_collection.ts +1 -1
- package/src/runtime_adapters.ts +19 -18
- package/src/sdam/server.ts +1 -1
- package/src/sessions.ts +2 -5
- package/src/utils.ts +1 -3
- package/tsconfig.json +9 -2
package/src/bulk/ordered.ts
CHANGED
|
@@ -17,13 +17,28 @@ export class OrderedBulkOperation extends BulkOperationBase {
|
|
|
17
17
|
batchType: BatchType,
|
|
18
18
|
document: Document | UpdateStatement | DeleteStatement
|
|
19
19
|
): this {
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
// Serialize the operation once here and reuse the bytes for both the size
|
|
21
|
+
// check/splitting and the wire message (via a DocumentSequence). Under
|
|
22
|
+
// auto-encryption the command is sent as a BSON array rather than a
|
|
23
|
+
// document sequence, so the buffer would never be reused; there we only
|
|
24
|
+
// measure the size and leave `buffer` undefined so nothing is retained on
|
|
25
|
+
// the batch.
|
|
26
|
+
let buffer: Uint8Array | undefined;
|
|
27
|
+
let bsonSize: number;
|
|
28
|
+
if (this.s.usingAutoEncryption) {
|
|
29
|
+
bsonSize = BSON.calculateObjectSize(document, {
|
|
30
|
+
checkKeys: false,
|
|
31
|
+
ignoreUndefined: false
|
|
32
|
+
} as any);
|
|
33
|
+
} else {
|
|
34
|
+
const bson = this.s.bsonOptions;
|
|
35
|
+
buffer = BSON.serialize(document, {
|
|
36
|
+
checkKeys: this.s.checkKeys,
|
|
37
|
+
ignoreUndefined: bson.ignoreUndefined,
|
|
38
|
+
serializeFunctions: bson.serializeFunctions
|
|
39
|
+
});
|
|
40
|
+
bsonSize = buffer.length;
|
|
41
|
+
}
|
|
27
42
|
|
|
28
43
|
// Throw error if the doc is bigger than the max BSON size
|
|
29
44
|
if (bsonSize >= this.s.maxBsonObjectSize)
|
|
@@ -75,6 +90,7 @@ export class OrderedBulkOperation extends BulkOperationBase {
|
|
|
75
90
|
|
|
76
91
|
this.s.currentBatch.originalIndexes.push(this.s.currentIndex);
|
|
77
92
|
this.s.currentBatch.operations.push(document);
|
|
93
|
+
if (buffer != null) this.s.currentBatch.serializedOperations.push(buffer);
|
|
78
94
|
this.s.currentBatchSize += 1;
|
|
79
95
|
this.s.currentBatchSizeBytes += maxKeySize + bsonSize;
|
|
80
96
|
this.s.currentIndex += 1;
|
package/src/bulk/unordered.ts
CHANGED
|
@@ -31,14 +31,28 @@ export class UnorderedBulkOperation extends BulkOperationBase {
|
|
|
31
31
|
batchType: BatchType,
|
|
32
32
|
document: Document | UpdateStatement | DeleteStatement
|
|
33
33
|
): this {
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
// Serialize the operation once here and reuse the bytes for both the size
|
|
35
|
+
// check/splitting and the wire message (via a DocumentSequence). Under
|
|
36
|
+
// auto-encryption the command is sent as a BSON array rather than a
|
|
37
|
+
// document sequence, so the buffer would never be reused; there we only
|
|
38
|
+
// measure the size and leave `buffer` undefined so nothing is retained on
|
|
39
|
+
// the batch.
|
|
40
|
+
let buffer: Uint8Array | undefined;
|
|
41
|
+
let bsonSize: number;
|
|
42
|
+
if (this.s.usingAutoEncryption) {
|
|
43
|
+
bsonSize = BSON.calculateObjectSize(document, {
|
|
44
|
+
checkKeys: false,
|
|
45
|
+
ignoreUndefined: false
|
|
46
|
+
} as any);
|
|
47
|
+
} else {
|
|
48
|
+
const bson = this.s.bsonOptions;
|
|
49
|
+
buffer = BSON.serialize(document, {
|
|
50
|
+
checkKeys: this.s.checkKeys,
|
|
51
|
+
ignoreUndefined: bson.ignoreUndefined,
|
|
52
|
+
serializeFunctions: bson.serializeFunctions
|
|
53
|
+
});
|
|
54
|
+
bsonSize = buffer.length;
|
|
55
|
+
}
|
|
42
56
|
|
|
43
57
|
// Throw error if the doc is bigger than the max BSON size
|
|
44
58
|
if (bsonSize >= this.s.maxBsonObjectSize) {
|
|
@@ -90,6 +104,7 @@ export class UnorderedBulkOperation extends BulkOperationBase {
|
|
|
90
104
|
}
|
|
91
105
|
|
|
92
106
|
this.s.currentBatch.operations.push(document);
|
|
107
|
+
if (buffer != null) this.s.currentBatch.serializedOperations.push(buffer);
|
|
93
108
|
this.s.currentBatch.originalIndexes.push(this.s.currentIndex);
|
|
94
109
|
this.s.currentIndex = this.s.currentIndex + 1;
|
|
95
110
|
|
|
@@ -11,6 +11,7 @@ import { type Abortable } from '../mongo_types';
|
|
|
11
11
|
import { MongoDBCollectionNamespace } from '../utils';
|
|
12
12
|
import { autoSelectSocketOptions } from './client_encryption';
|
|
13
13
|
import { defaultErrorWrapper, MongoCryptInvalidArgumentError } from './errors';
|
|
14
|
+
import { type CSFLEKMSTlsOptions, type KMSConnectCallback } from './kms_options';
|
|
14
15
|
import { MongocryptdManager } from './mongocryptd_manager';
|
|
15
16
|
import {
|
|
16
17
|
type CredentialProviders,
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
type KMSProviders,
|
|
19
20
|
refreshKMSCredentials
|
|
20
21
|
} from './providers';
|
|
21
|
-
import {
|
|
22
|
+
import { StateMachine } from './state_machine';
|
|
22
23
|
|
|
23
24
|
/** @public */
|
|
24
25
|
export interface AutoEncryptionOptions {
|
|
@@ -110,6 +111,12 @@ export interface AutoEncryptionOptions {
|
|
|
110
111
|
proxyOptions?: ProxyOptions;
|
|
111
112
|
/** The TLS options to use connecting to the KMS provider */
|
|
112
113
|
tlsOptions?: CSFLEKMSTlsOptions;
|
|
114
|
+
/**
|
|
115
|
+
* A callback that establishes the socket used to connect to a KMS host, e.g. to route KMS
|
|
116
|
+
* requests through an HTTP proxy via the HTTP CONNECT method. Mutually exclusive with `proxyOptions`.
|
|
117
|
+
* See {@link KMSConnectCallback} for a usage example.
|
|
118
|
+
*/
|
|
119
|
+
kmsConnectCallback?: KMSConnectCallback;
|
|
113
120
|
}
|
|
114
121
|
|
|
115
122
|
/**
|
|
@@ -156,6 +163,7 @@ export class AutoEncrypter {
|
|
|
156
163
|
_metaDataClient: MongoClient;
|
|
157
164
|
_proxyOptions: ProxyOptions;
|
|
158
165
|
_tlsOptions: CSFLEKMSTlsOptions;
|
|
166
|
+
_kmsConnectCallback?: KMSConnectCallback;
|
|
159
167
|
_kmsProviders: KMSProviders;
|
|
160
168
|
_bypassMongocryptdAndCryptShared: boolean;
|
|
161
169
|
_contextCounter: number;
|
|
@@ -242,7 +250,13 @@ export class AutoEncrypter {
|
|
|
242
250
|
this._keyVaultClient = options.keyVaultClient || client;
|
|
243
251
|
this._metaDataClient = options.metadataClient || client;
|
|
244
252
|
this._proxyOptions = options.proxyOptions || {};
|
|
253
|
+
if (this._proxyOptions.proxyHost && options.kmsConnectCallback) {
|
|
254
|
+
throw new MongoCryptInvalidArgumentError(
|
|
255
|
+
'Cannot set both proxyOptions and kmsConnectCallback'
|
|
256
|
+
);
|
|
257
|
+
}
|
|
245
258
|
this._tlsOptions = options.tlsOptions || {};
|
|
259
|
+
this._kmsConnectCallback = options.kmsConnectCallback;
|
|
246
260
|
this._kmsProviders = options.kmsProviders || {};
|
|
247
261
|
this._credentialProviders = options.credentialProviders;
|
|
248
262
|
|
|
@@ -417,7 +431,8 @@ export class AutoEncrypter {
|
|
|
417
431
|
promoteLongs: false,
|
|
418
432
|
proxyOptions: this._proxyOptions,
|
|
419
433
|
tlsOptions: this._tlsOptions,
|
|
420
|
-
socketOptions: autoSelectSocketOptions(this._client.s.options)
|
|
434
|
+
socketOptions: autoSelectSocketOptions(this._client.s.options),
|
|
435
|
+
kmsConnectCallback: this._kmsConnectCallback
|
|
421
436
|
});
|
|
422
437
|
|
|
423
438
|
return deserialize(await stateMachine.execute(this, context, options), {
|
|
@@ -443,7 +458,8 @@ export class AutoEncrypter {
|
|
|
443
458
|
...options,
|
|
444
459
|
proxyOptions: this._proxyOptions,
|
|
445
460
|
tlsOptions: this._tlsOptions,
|
|
446
|
-
socketOptions: autoSelectSocketOptions(this._client.s.options)
|
|
461
|
+
socketOptions: autoSelectSocketOptions(this._client.s.options),
|
|
462
|
+
kmsConnectCallback: this._kmsConnectCallback
|
|
447
463
|
});
|
|
448
464
|
|
|
449
465
|
return await stateMachine.execute(this, context, options);
|
|
@@ -31,6 +31,11 @@ import {
|
|
|
31
31
|
MongoCryptCreateEncryptedCollectionError,
|
|
32
32
|
MongoCryptInvalidArgumentError
|
|
33
33
|
} from './errors';
|
|
34
|
+
import {
|
|
35
|
+
type ClientEncryptionSocketOptions,
|
|
36
|
+
type CSFLEKMSTlsOptions,
|
|
37
|
+
type KMSConnectCallback
|
|
38
|
+
} from './kms_options';
|
|
34
39
|
import {
|
|
35
40
|
type ClientEncryptionDataKeyProvider,
|
|
36
41
|
type CredentialProviders,
|
|
@@ -38,11 +43,7 @@ import {
|
|
|
38
43
|
type KMSProviders,
|
|
39
44
|
refreshKMSCredentials
|
|
40
45
|
} from './providers/index';
|
|
41
|
-
import {
|
|
42
|
-
type ClientEncryptionSocketOptions,
|
|
43
|
-
type CSFLEKMSTlsOptions,
|
|
44
|
-
StateMachine
|
|
45
|
-
} from './state_machine';
|
|
46
|
+
import { StateMachine } from './state_machine';
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
49
|
* @public
|
|
@@ -75,6 +76,8 @@ export class ClientEncryption {
|
|
|
75
76
|
/** @internal */
|
|
76
77
|
_tlsOptions: CSFLEKMSTlsOptions;
|
|
77
78
|
/** @internal */
|
|
79
|
+
_kmsConnectCallback?: KMSConnectCallback;
|
|
80
|
+
/** @internal */
|
|
78
81
|
_kmsProviders: KMSProviders;
|
|
79
82
|
/** @internal */
|
|
80
83
|
_timeoutMS?: number;
|
|
@@ -125,7 +128,13 @@ export class ClientEncryption {
|
|
|
125
128
|
constructor(client: MongoClient, options: ClientEncryptionOptions) {
|
|
126
129
|
this._client = client;
|
|
127
130
|
this._proxyOptions = options.proxyOptions ?? {};
|
|
131
|
+
if (this._proxyOptions.proxyHost && options.kmsConnectCallback) {
|
|
132
|
+
throw new MongoCryptInvalidArgumentError(
|
|
133
|
+
'Cannot set both proxyOptions and kmsConnectCallback'
|
|
134
|
+
);
|
|
135
|
+
}
|
|
128
136
|
this._tlsOptions = options.tlsOptions ?? {};
|
|
137
|
+
this._kmsConnectCallback = options.kmsConnectCallback;
|
|
129
138
|
this._kmsProviders = options.kmsProviders || {};
|
|
130
139
|
const { timeoutMS } = resolveTimeoutOptions(client, options);
|
|
131
140
|
this._timeoutMS = timeoutMS;
|
|
@@ -226,7 +235,8 @@ export class ClientEncryption {
|
|
|
226
235
|
const stateMachine = new StateMachine({
|
|
227
236
|
proxyOptions: this._proxyOptions,
|
|
228
237
|
tlsOptions: this._tlsOptions,
|
|
229
|
-
socketOptions: autoSelectSocketOptions(this._client.s.options)
|
|
238
|
+
socketOptions: autoSelectSocketOptions(this._client.s.options),
|
|
239
|
+
kmsConnectCallback: this._kmsConnectCallback
|
|
230
240
|
});
|
|
231
241
|
|
|
232
242
|
const timeoutContext =
|
|
@@ -295,7 +305,8 @@ export class ClientEncryption {
|
|
|
295
305
|
const stateMachine = new StateMachine({
|
|
296
306
|
proxyOptions: this._proxyOptions,
|
|
297
307
|
tlsOptions: this._tlsOptions,
|
|
298
|
-
socketOptions: autoSelectSocketOptions(this._client.s.options)
|
|
308
|
+
socketOptions: autoSelectSocketOptions(this._client.s.options),
|
|
309
|
+
kmsConnectCallback: this._kmsConnectCallback
|
|
299
310
|
});
|
|
300
311
|
|
|
301
312
|
const timeoutContext = TimeoutContext.create(
|
|
@@ -699,7 +710,8 @@ export class ClientEncryption {
|
|
|
699
710
|
const stateMachine = new StateMachine({
|
|
700
711
|
proxyOptions: this._proxyOptions,
|
|
701
712
|
tlsOptions: this._tlsOptions,
|
|
702
|
-
socketOptions: autoSelectSocketOptions(this._client.s.options)
|
|
713
|
+
socketOptions: autoSelectSocketOptions(this._client.s.options),
|
|
714
|
+
kmsConnectCallback: this._kmsConnectCallback
|
|
703
715
|
});
|
|
704
716
|
|
|
705
717
|
const timeoutContext =
|
|
@@ -797,7 +809,8 @@ export class ClientEncryption {
|
|
|
797
809
|
const stateMachine = new StateMachine({
|
|
798
810
|
proxyOptions: this._proxyOptions,
|
|
799
811
|
tlsOptions: this._tlsOptions,
|
|
800
|
-
socketOptions: autoSelectSocketOptions(this._client.s.options)
|
|
812
|
+
socketOptions: autoSelectSocketOptions(this._client.s.options),
|
|
813
|
+
kmsConnectCallback: this._kmsConnectCallback
|
|
801
814
|
});
|
|
802
815
|
const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);
|
|
803
816
|
|
|
@@ -961,6 +974,13 @@ export interface ClientEncryptionOptions {
|
|
|
961
974
|
*/
|
|
962
975
|
tlsOptions?: CSFLEKMSTlsOptions;
|
|
963
976
|
|
|
977
|
+
/**
|
|
978
|
+
* A callback that establishes the socket used to connect to a KMS host, e.g. to route KMS
|
|
979
|
+
* requests through an HTTP proxy via the HTTP CONNECT method. Mutually exclusive with `proxyOptions`.
|
|
980
|
+
* See {@link KMSConnectCallback} for a usage example.
|
|
981
|
+
*/
|
|
982
|
+
kmsConnectCallback?: KMSConnectCallback;
|
|
983
|
+
|
|
964
984
|
/**
|
|
965
985
|
* Sets the expiration time for the DEK in the cache in milliseconds. Defaults to 60000. 0 means no timeout.
|
|
966
986
|
*/
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type Duplex } from 'stream';
|
|
2
|
+
|
|
3
|
+
import { type MongoClientOptions } from '../mongo_client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @public
|
|
7
|
+
*
|
|
8
|
+
* TLS options to use when connecting. The spec specifically calls out which insecure
|
|
9
|
+
* tls options are not allowed:
|
|
10
|
+
*
|
|
11
|
+
* - tlsAllowInvalidCertificates
|
|
12
|
+
* - tlsAllowInvalidHostnames
|
|
13
|
+
* - tlsInsecure
|
|
14
|
+
*
|
|
15
|
+
* These options are not included in the type, and are ignored if provided.
|
|
16
|
+
*/
|
|
17
|
+
export type ClientEncryptionTlsOptions = Pick<
|
|
18
|
+
MongoClientOptions,
|
|
19
|
+
'tlsCAFile' | 'tlsCertificateKeyFile' | 'tlsCertificateKeyFilePassword' | 'secureContext'
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
/** @public */
|
|
23
|
+
export type CSFLEKMSTlsOptions = {
|
|
24
|
+
aws?: ClientEncryptionTlsOptions;
|
|
25
|
+
gcp?: ClientEncryptionTlsOptions;
|
|
26
|
+
kmip?: ClientEncryptionTlsOptions;
|
|
27
|
+
local?: ClientEncryptionTlsOptions;
|
|
28
|
+
azure?: ClientEncryptionTlsOptions;
|
|
29
|
+
|
|
30
|
+
[key: string]: ClientEncryptionTlsOptions | undefined;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @public
|
|
35
|
+
*
|
|
36
|
+
* Socket options to use for KMS requests.
|
|
37
|
+
*/
|
|
38
|
+
export type ClientEncryptionSocketOptions = Pick<
|
|
39
|
+
MongoClientOptions,
|
|
40
|
+
'autoSelectFamily' | 'autoSelectFamilyAttemptTimeout'
|
|
41
|
+
>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @public
|
|
45
|
+
*
|
|
46
|
+
* A callback that establishes the connection to a KMS host.
|
|
47
|
+
*
|
|
48
|
+
* When provided on `AutoEncryptionOptions` or `ClientEncryptionOptions`, the driver invokes this
|
|
49
|
+
* callback instead of connecting to the KMS host itself, passing the target `host` and `port`. The
|
|
50
|
+
* callback MUST return a `Duplex` stream connected to the KMS host, either directly or tunneled
|
|
51
|
+
* through a proxy; a `net.Socket` satisfies this, as does any other `Duplex`. The returned stream is
|
|
52
|
+
* passed to Node.js' `tls.connect()` as its `socket`, and the driver performs the KMS host's TLS
|
|
53
|
+
* handshake over it using the KMS provider's configured TLS options. The callback therefore MUST NOT
|
|
54
|
+
* perform the KMS host's TLS handshake itself, though it MAY use TLS for its own transport, e.g. when
|
|
55
|
+
* connecting to an HTTPS proxy. This enables routing KMS requests through an HTTP proxy via the HTTP
|
|
56
|
+
* CONNECT method.
|
|
57
|
+
*
|
|
58
|
+
* When the operation has a client-side operation timeout (CSOT) configured, `timeoutMS` is the
|
|
59
|
+
* remaining time budget in milliseconds; it is `undefined` otherwise. The `signal` aborts when the
|
|
60
|
+
* connection attempt exceeds that budget; the callback should stop connecting and reject when it fires.
|
|
61
|
+
*
|
|
62
|
+
* @example <caption>Route KMS requests through an HTTP proxy using the HTTP CONNECT method</caption>
|
|
63
|
+
* ```ts
|
|
64
|
+
* import * as net from 'net';
|
|
65
|
+
*
|
|
66
|
+
* const kmsConnectCallback: KMSConnectCallback = ({ host, port, signal }) =>
|
|
67
|
+
* new Promise((resolve, reject) => {
|
|
68
|
+
* // Open a plain connection to the proxy, not to the KMS host.
|
|
69
|
+
* const socket = net.connect({ host: 'proxy.example.com', port: 8080, signal });
|
|
70
|
+
* socket.once('error', reject);
|
|
71
|
+
* socket.once('connect', () => {
|
|
72
|
+
* // Ask the proxy to tunnel to the KMS host, then hand the socket back for the driver's TLS.
|
|
73
|
+
* socket.write(`CONNECT ${host}:${port} HTTP/1.1\r\nHost: ${host}:${port}\r\n\r\n`);
|
|
74
|
+
* socket.once('data', chunk => {
|
|
75
|
+
* if (chunk.toString('utf8').startsWith('HTTP/1.1 200')) resolve(socket);
|
|
76
|
+
* else reject(new Error('Proxy refused the CONNECT request'));
|
|
77
|
+
* });
|
|
78
|
+
* });
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* const clientEncryption = new ClientEncryption(keyVaultClient, {
|
|
82
|
+
* keyVaultNamespace,
|
|
83
|
+
* kmsProviders,
|
|
84
|
+
* kmsConnectCallback
|
|
85
|
+
* });
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export type KMSConnectCallback = (options: {
|
|
89
|
+
host: string;
|
|
90
|
+
port: number;
|
|
91
|
+
timeoutMS?: number;
|
|
92
|
+
signal: AbortSignal;
|
|
93
|
+
}) => Promise<Duplex>;
|
|
@@ -15,7 +15,7 @@ import { type ProxyOptions } from '../cmap/connection';
|
|
|
15
15
|
import { CursorTimeoutContext } from '../cursor/abstract_cursor';
|
|
16
16
|
import { getSocks, type SocksLib } from '../deps';
|
|
17
17
|
import { MongoOperationTimeoutError } from '../error';
|
|
18
|
-
import { type MongoClient
|
|
18
|
+
import { type MongoClient } from '../mongo_client';
|
|
19
19
|
import { type Abortable } from '../mongo_types';
|
|
20
20
|
import { type CollectionInfo } from '../operations/list_collections';
|
|
21
21
|
import { Timeout, type TimeoutContext, TimeoutError } from '../timeout';
|
|
@@ -28,6 +28,12 @@ import {
|
|
|
28
28
|
} from '../utils';
|
|
29
29
|
import { autoSelectSocketOptions, type DataKey } from './client_encryption';
|
|
30
30
|
import { MongoCryptError } from './errors';
|
|
31
|
+
import {
|
|
32
|
+
type ClientEncryptionSocketOptions,
|
|
33
|
+
type ClientEncryptionTlsOptions,
|
|
34
|
+
type CSFLEKMSTlsOptions,
|
|
35
|
+
type KMSConnectCallback
|
|
36
|
+
} from './kms_options';
|
|
31
37
|
import { type MongocryptdManager } from './mongocryptd_manager';
|
|
32
38
|
import { type KMSProviders } from './providers';
|
|
33
39
|
|
|
@@ -93,44 +99,6 @@ declare module 'mongodb-client-encryption' {
|
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
101
|
|
|
96
|
-
/**
|
|
97
|
-
* @public
|
|
98
|
-
*
|
|
99
|
-
* TLS options to use when connecting. The spec specifically calls out which insecure
|
|
100
|
-
* tls options are not allowed:
|
|
101
|
-
*
|
|
102
|
-
* - tlsAllowInvalidCertificates
|
|
103
|
-
* - tlsAllowInvalidHostnames
|
|
104
|
-
* - tlsInsecure
|
|
105
|
-
*
|
|
106
|
-
* These options are not included in the type, and are ignored if provided.
|
|
107
|
-
*/
|
|
108
|
-
export type ClientEncryptionTlsOptions = Pick<
|
|
109
|
-
MongoClientOptions,
|
|
110
|
-
'tlsCAFile' | 'tlsCertificateKeyFile' | 'tlsCertificateKeyFilePassword' | 'secureContext'
|
|
111
|
-
>;
|
|
112
|
-
|
|
113
|
-
/** @public */
|
|
114
|
-
export type CSFLEKMSTlsOptions = {
|
|
115
|
-
aws?: ClientEncryptionTlsOptions;
|
|
116
|
-
gcp?: ClientEncryptionTlsOptions;
|
|
117
|
-
kmip?: ClientEncryptionTlsOptions;
|
|
118
|
-
local?: ClientEncryptionTlsOptions;
|
|
119
|
-
azure?: ClientEncryptionTlsOptions;
|
|
120
|
-
|
|
121
|
-
[key: string]: ClientEncryptionTlsOptions | undefined;
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* @public
|
|
126
|
-
*
|
|
127
|
-
* Socket options to use for KMS requests.
|
|
128
|
-
*/
|
|
129
|
-
export type ClientEncryptionSocketOptions = Pick<
|
|
130
|
-
MongoClientOptions,
|
|
131
|
-
'autoSelectFamily' | 'autoSelectFamilyAttemptTimeout'
|
|
132
|
-
>;
|
|
133
|
-
|
|
134
102
|
/**
|
|
135
103
|
* This is kind of a hack. For `rewrapManyDataKey`, we have tests that
|
|
136
104
|
* guarantee that when there are no matching keys, `rewrapManyDataKey` returns
|
|
@@ -173,6 +141,9 @@ export type StateMachineOptions = {
|
|
|
173
141
|
|
|
174
142
|
/** Socket specific options we support. */
|
|
175
143
|
socketOptions: ClientEncryptionSocketOptions;
|
|
144
|
+
|
|
145
|
+
/** Optional callback that establishes the KMS socket, if set. */
|
|
146
|
+
kmsConnectCallback?: KMSConnectCallback;
|
|
176
147
|
} & Pick<BSONSerializeOptions, 'promoteLongs' | 'promoteValues'>;
|
|
177
148
|
|
|
178
149
|
/**
|
|
@@ -388,9 +359,55 @@ export class StateMachine {
|
|
|
388
359
|
}
|
|
389
360
|
|
|
390
361
|
let abortListener;
|
|
362
|
+
let kmsRequestTimeout: Timeout | undefined;
|
|
391
363
|
|
|
392
364
|
try {
|
|
393
|
-
|
|
365
|
+
options?.signal?.throwIfAborted();
|
|
366
|
+
|
|
367
|
+
if (this.options.kmsConnectCallback) {
|
|
368
|
+
const remainingTimeMS = options?.timeoutContext?.csotEnabled()
|
|
369
|
+
? options.timeoutContext.getRemainingTimeMSOrThrow(
|
|
370
|
+
`KMS request timed out after ${options.timeoutContext.timeoutMS}ms`
|
|
371
|
+
)
|
|
372
|
+
: undefined;
|
|
373
|
+
// A non-finite budget (timeoutMS: 0) means no timeout; otherwise back the callback with one.
|
|
374
|
+
const timeoutMS = Number.isFinite(remainingTimeMS) ? remainingTimeMS : undefined;
|
|
375
|
+
const controller = new AbortController();
|
|
376
|
+
// Forward the operation's own abort signal so aborting the operation also aborts the callback.
|
|
377
|
+
const forwardAbort = addAbortListener(options?.signal, function () {
|
|
378
|
+
controller.abort(this.reason);
|
|
379
|
+
});
|
|
380
|
+
const connectPromise = this.options.kmsConnectCallback({
|
|
381
|
+
host: socketOptions.host,
|
|
382
|
+
port: socketOptions.port,
|
|
383
|
+
timeoutMS,
|
|
384
|
+
signal: controller.signal
|
|
385
|
+
});
|
|
386
|
+
// If the callback resolves a socket after we already aborted, close it so it does not leak.
|
|
387
|
+
connectPromise.then(
|
|
388
|
+
resolvedSocket => {
|
|
389
|
+
if (controller.signal.aborted) resolvedSocket.destroy();
|
|
390
|
+
},
|
|
391
|
+
() => null
|
|
392
|
+
);
|
|
393
|
+
const timeout = timeoutMS ? Timeout.expires(timeoutMS) : undefined;
|
|
394
|
+
try {
|
|
395
|
+
socketOptions.socket = timeout
|
|
396
|
+
? await Promise.race([connectPromise, timeout])
|
|
397
|
+
: await connectPromise;
|
|
398
|
+
} catch (err) {
|
|
399
|
+
if (TimeoutError.is(err)) {
|
|
400
|
+
const timeoutError = new MongoOperationTimeoutError('KMS request timed out');
|
|
401
|
+
// Abort with the timeout error as the reason so the callback sees it on `signal.reason`.
|
|
402
|
+
controller.abort(timeoutError);
|
|
403
|
+
throw timeoutError;
|
|
404
|
+
}
|
|
405
|
+
throw onerror(err);
|
|
406
|
+
} finally {
|
|
407
|
+
timeout?.clear();
|
|
408
|
+
forwardAbort?.[kDispose]();
|
|
409
|
+
}
|
|
410
|
+
} else if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) {
|
|
394
411
|
netSocket = new net.Socket();
|
|
395
412
|
|
|
396
413
|
const {
|
|
@@ -465,20 +482,24 @@ export class StateMachine {
|
|
|
465
482
|
resolve();
|
|
466
483
|
}
|
|
467
484
|
});
|
|
468
|
-
|
|
469
|
-
?
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
485
|
+
const remainingTimeMS = options?.timeoutContext?.csotEnabled()
|
|
486
|
+
? options.timeoutContext.getRemainingTimeMSOrThrow(
|
|
487
|
+
`KMS request timed out after ${options.timeoutContext.timeoutMS}ms`
|
|
488
|
+
)
|
|
489
|
+
: undefined;
|
|
490
|
+
const timeoutMS = Number.isFinite(remainingTimeMS) ? remainingTimeMS : undefined;
|
|
491
|
+
kmsRequestTimeout = timeoutMS ? Timeout.expires(timeoutMS) : undefined;
|
|
492
|
+
await (kmsRequestTimeout
|
|
493
|
+
? Promise.race([willResolveKmsRequest, kmsRequestTimeout])
|
|
473
494
|
: willResolveKmsRequest);
|
|
474
495
|
} catch (error) {
|
|
475
|
-
if (error
|
|
476
|
-
throw new MongoOperationTimeoutError('KMS request timed out');
|
|
496
|
+
if (TimeoutError.is(error)) throw new MongoOperationTimeoutError('KMS request timed out');
|
|
477
497
|
throw error;
|
|
478
498
|
} finally {
|
|
479
499
|
// There's no need for any more activity on this socket at this point.
|
|
480
500
|
destroySockets();
|
|
481
501
|
abortListener?.[kDispose]();
|
|
502
|
+
kmsRequestTimeout?.clear();
|
|
482
503
|
}
|
|
483
504
|
}
|
|
484
505
|
|
package/src/cmap/auth/gssapi.ts
CHANGED
|
@@ -69,10 +69,7 @@ export class GSSAPI extends AuthProvider {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
async function makeKerberosClient({
|
|
72
|
-
options: {
|
|
73
|
-
hostAddress,
|
|
74
|
-
runtime: { os }
|
|
75
|
-
},
|
|
72
|
+
options: { hostAddress, runtime },
|
|
76
73
|
credentials
|
|
77
74
|
}: AuthContext): Promise<KerberosClient> {
|
|
78
75
|
if (!hostAddress || typeof hostAddress.host !== 'string' || !credentials) {
|
|
@@ -81,6 +78,8 @@ async function makeKerberosClient({
|
|
|
81
78
|
);
|
|
82
79
|
}
|
|
83
80
|
|
|
81
|
+
const { os } = await runtime;
|
|
82
|
+
|
|
84
83
|
loadKrb();
|
|
85
84
|
if ('kModuleError' in krb) {
|
|
86
85
|
throw krb['kModuleError'];
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { type Binary, type BSONSerializeOptions, ByteUtils } from '../../bson';
|
|
2
2
|
import * as BSON from '../../bson';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
MongoMissingCredentialsError,
|
|
6
|
-
MongoRuntimeError
|
|
7
|
-
} from '../../error';
|
|
8
|
-
import { maxWireVersion, ns, randomBytes } from '../../utils';
|
|
3
|
+
import { MongoMissingCredentialsError, MongoRuntimeError } from '../../error';
|
|
4
|
+
import { ns, randomBytes } from '../../utils';
|
|
9
5
|
import { type AuthContext, AuthProvider } from './auth_provider';
|
|
10
6
|
import {
|
|
11
7
|
type AWSCredentialProvider,
|
|
@@ -45,12 +41,6 @@ export class MongoDBAWS extends AuthProvider {
|
|
|
45
41
|
throw new MongoMissingCredentialsError('AuthContext must provide credentials.');
|
|
46
42
|
}
|
|
47
43
|
|
|
48
|
-
if (maxWireVersion(connection) < 9) {
|
|
49
|
-
throw new MongoCompatibilityError(
|
|
50
|
-
'MONGODB-AWS authentication requires MongoDB version 4.4 or later'
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
44
|
authContext.credentials = await makeTempCredentials(
|
|
55
45
|
authContext.credentials,
|
|
56
46
|
this.credentialFetcher
|
|
@@ -32,7 +32,7 @@ export class CommandStartedEvent {
|
|
|
32
32
|
/**
|
|
33
33
|
* Server generated connection id
|
|
34
34
|
* Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId"
|
|
35
|
-
* from the server
|
|
35
|
+
* from the server.
|
|
36
36
|
*/
|
|
37
37
|
serverConnectionId: bigint | null;
|
|
38
38
|
serviceId?: ObjectId;
|
|
@@ -88,7 +88,7 @@ export class CommandSucceededEvent {
|
|
|
88
88
|
connectionId?: string | number;
|
|
89
89
|
/**
|
|
90
90
|
* Server generated connection id
|
|
91
|
-
* Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server
|
|
91
|
+
* Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server.
|
|
92
92
|
*/
|
|
93
93
|
serverConnectionId: bigint | null;
|
|
94
94
|
requestId: number;
|
|
@@ -148,7 +148,7 @@ export class CommandFailedEvent {
|
|
|
148
148
|
connectionId?: string | number;
|
|
149
149
|
/**
|
|
150
150
|
* Server generated connection id
|
|
151
|
-
* Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server
|
|
151
|
+
* Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server.
|
|
152
152
|
*/
|
|
153
153
|
serverConnectionId: bigint | null;
|
|
154
154
|
requestId: number;
|
|
@@ -249,13 +249,12 @@ const LEGACY_FIND_OPTIONS_MAP = {
|
|
|
249
249
|
function extractCommand(command: WriteProtocolMessageType): Document {
|
|
250
250
|
if (command instanceof OpMsgRequest) {
|
|
251
251
|
const cmd = { ...command.command };
|
|
252
|
-
// For OP_MSG
|
|
253
|
-
// array
|
|
254
|
-
|
|
255
|
-
cmd
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
cmd.nsInfo = cmd.nsInfo.documents;
|
|
252
|
+
// For OP_MSG payload type 1, replace any document-sequence field with its
|
|
253
|
+
// documents array for monitoring (ops, nsInfo, documents, updates, deletes).
|
|
254
|
+
for (const key of Object.keys(cmd)) {
|
|
255
|
+
if (cmd[key] instanceof DocumentSequence) {
|
|
256
|
+
cmd[key] = cmd[key].documents;
|
|
257
|
+
}
|
|
259
258
|
}
|
|
260
259
|
return cmd;
|
|
261
260
|
}
|
package/src/cmap/commands.ts
CHANGED
|
@@ -500,6 +500,23 @@ export class DocumentSequence {
|
|
|
500
500
|
}
|
|
501
501
|
}
|
|
502
502
|
|
|
503
|
+
/**
|
|
504
|
+
* Build a DocumentSequence from documents that have already been serialized,
|
|
505
|
+
* reusing the provided buffers instead of re-serializing.
|
|
506
|
+
* @internal
|
|
507
|
+
*/
|
|
508
|
+
export function makeDocumentSequence(
|
|
509
|
+
field: string,
|
|
510
|
+
documents: Document[],
|
|
511
|
+
serialized: Uint8Array[]
|
|
512
|
+
): DocumentSequence {
|
|
513
|
+
const sequence = new DocumentSequence(field);
|
|
514
|
+
for (let i = 0; i < documents.length; i++) {
|
|
515
|
+
sequence.push(documents[i], serialized[i]);
|
|
516
|
+
}
|
|
517
|
+
return sequence;
|
|
518
|
+
}
|
|
519
|
+
|
|
503
520
|
/** @internal */
|
|
504
521
|
export class OpMsgRequest {
|
|
505
522
|
requestId: number;
|
package/src/cmap/connect.ts
CHANGED
|
@@ -174,7 +174,7 @@ export async function performInitialHandshake(
|
|
|
174
174
|
|
|
175
175
|
if (error instanceof MongoError) {
|
|
176
176
|
error.addErrorLabel(MongoErrorLabel.HandshakeError);
|
|
177
|
-
if (needsRetryableWriteLabel(error
|
|
177
|
+
if (needsRetryableWriteLabel(error)) {
|
|
178
178
|
error.addErrorLabel(MongoErrorLabel.RetryableWriteError);
|
|
179
179
|
}
|
|
180
180
|
}
|
|
@@ -224,7 +224,7 @@ export interface HandshakeDocument extends Document {
|
|
|
224
224
|
compression: string[];
|
|
225
225
|
saslSupportedMechs?: string;
|
|
226
226
|
loadBalanced?: boolean;
|
|
227
|
-
backpressure:
|
|
227
|
+
backpressure: '2';
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
/**
|
|
@@ -242,7 +242,7 @@ export async function prepareHandshakeDocument(
|
|
|
242
242
|
|
|
243
243
|
const handshakeDoc: HandshakeDocument = {
|
|
244
244
|
[serverApi?.version || options.loadBalanced === true ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
|
|
245
|
-
backpressure:
|
|
245
|
+
backpressure: '2',
|
|
246
246
|
helloOk: true,
|
|
247
247
|
client: clientMetadata,
|
|
248
248
|
compression: compressors
|