mongo works
This commit is contained in:
parent
ae6ccd559f
commit
125107da50
937 changed files with 205033 additions and 2 deletions
468
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/auto_encrypter.ts
generated
vendored
Normal file
468
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/auto_encrypter.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
import {
|
||||
type MongoCrypt,
|
||||
type MongoCryptConstructor,
|
||||
type MongoCryptOptions
|
||||
} from 'mongodb-client-encryption';
|
||||
import * as net from 'net';
|
||||
|
||||
import { deserialize, type Document, serialize } from '../bson';
|
||||
import { type CommandOptions, type ProxyOptions } from '../cmap/connection';
|
||||
import { kDecorateResult } from '../constants';
|
||||
import { getMongoDBClientEncryption } from '../deps';
|
||||
import { MongoRuntimeError } from '../error';
|
||||
import { MongoClient, type MongoClientOptions } from '../mongo_client';
|
||||
import { type Abortable } from '../mongo_types';
|
||||
import { MongoDBCollectionNamespace } from '../utils';
|
||||
import { autoSelectSocketOptions } from './client_encryption';
|
||||
import * as cryptoCallbacks from './crypto_callbacks';
|
||||
import { MongoCryptInvalidArgumentError } from './errors';
|
||||
import { MongocryptdManager } from './mongocryptd_manager';
|
||||
import {
|
||||
type CredentialProviders,
|
||||
isEmptyCredentials,
|
||||
type KMSProviders,
|
||||
refreshKMSCredentials
|
||||
} from './providers';
|
||||
import { type CSFLEKMSTlsOptions, StateMachine } from './state_machine';
|
||||
|
||||
/** @public */
|
||||
export interface AutoEncryptionOptions {
|
||||
/** @internal client for metadata lookups */
|
||||
metadataClient?: MongoClient;
|
||||
/** A `MongoClient` used to fetch keys from a key vault */
|
||||
keyVaultClient?: MongoClient;
|
||||
/** The namespace where keys are stored in the key vault */
|
||||
keyVaultNamespace?: string;
|
||||
/** Configuration options that are used by specific KMS providers during key generation, encryption, and decryption. */
|
||||
kmsProviders?: KMSProviders;
|
||||
/** Configuration options for custom credential providers. */
|
||||
credentialProviders?: CredentialProviders;
|
||||
/**
|
||||
* A map of namespaces to a local JSON schema for encryption
|
||||
*
|
||||
* **NOTE**: Supplying options.schemaMap provides more security than relying on JSON Schemas obtained from the server.
|
||||
* It protects against a malicious server advertising a false JSON Schema, which could trick the client into sending decrypted data that should be encrypted.
|
||||
* Schemas supplied in the schemaMap only apply to configuring automatic encryption for Client-Side Field Level Encryption.
|
||||
* Other validation rules in the JSON schema will not be enforced by the driver and will result in an error.
|
||||
*/
|
||||
schemaMap?: Document;
|
||||
/** Supply a schema for the encrypted fields in the document */
|
||||
encryptedFieldsMap?: Document;
|
||||
/** Allows the user to bypass auto encryption, maintaining implicit decryption */
|
||||
bypassAutoEncryption?: boolean;
|
||||
/** Allows users to bypass query analysis */
|
||||
bypassQueryAnalysis?: boolean;
|
||||
options?: {
|
||||
/** An optional hook to catch logging messages from the underlying encryption engine */
|
||||
logger?: (level: AutoEncryptionLoggerLevel, message: string) => void;
|
||||
};
|
||||
extraOptions?: {
|
||||
/**
|
||||
* A local process the driver communicates with to determine how to encrypt values in a command.
|
||||
* Defaults to "mongodb://%2Fvar%2Fmongocryptd.sock" if domain sockets are available or "mongodb://localhost:27020" otherwise
|
||||
*/
|
||||
mongocryptdURI?: string;
|
||||
/** If true, autoEncryption will not attempt to spawn a mongocryptd before connecting */
|
||||
mongocryptdBypassSpawn?: boolean;
|
||||
/** The path to the mongocryptd executable on the system */
|
||||
mongocryptdSpawnPath?: string;
|
||||
/** Command line arguments to use when auto-spawning a mongocryptd */
|
||||
mongocryptdSpawnArgs?: string[];
|
||||
/**
|
||||
* Full path to a MongoDB Crypt shared library to be used (instead of mongocryptd).
|
||||
*
|
||||
* This needs to be the path to the file itself, not a directory.
|
||||
* It can be an absolute or relative path. If the path is relative and
|
||||
* its first component is `$ORIGIN`, it will be replaced by the directory
|
||||
* containing the mongodb-client-encryption native addon file. Otherwise,
|
||||
* the path will be interpreted relative to the current working directory.
|
||||
*
|
||||
* Currently, loading different MongoDB Crypt shared library files from different
|
||||
* MongoClients in the same process is not supported.
|
||||
*
|
||||
* If this option is provided and no MongoDB Crypt shared library could be loaded
|
||||
* from the specified location, creating the MongoClient will fail.
|
||||
*
|
||||
* If this option is not provided and `cryptSharedLibRequired` is not specified,
|
||||
* the AutoEncrypter will attempt to spawn and/or use mongocryptd according
|
||||
* to the mongocryptd-specific `extraOptions` options.
|
||||
*
|
||||
* Specifying a path prevents mongocryptd from being used as a fallback.
|
||||
*
|
||||
* Requires the MongoDB Crypt shared library, available in MongoDB 6.0 or higher.
|
||||
*/
|
||||
cryptSharedLibPath?: string;
|
||||
/**
|
||||
* If specified, never use mongocryptd and instead fail when the MongoDB Crypt
|
||||
* shared library could not be loaded.
|
||||
*
|
||||
* This is always true when `cryptSharedLibPath` is specified.
|
||||
*
|
||||
* Requires the MongoDB Crypt shared library, available in MongoDB 6.0 or higher.
|
||||
*/
|
||||
cryptSharedLibRequired?: boolean;
|
||||
/**
|
||||
* Search paths for a MongoDB Crypt shared library to be used (instead of mongocryptd)
|
||||
* Only for driver testing!
|
||||
* @internal
|
||||
*/
|
||||
cryptSharedLibSearchPaths?: string[];
|
||||
};
|
||||
proxyOptions?: ProxyOptions;
|
||||
/** The TLS options to use connecting to the KMS provider */
|
||||
tlsOptions?: CSFLEKMSTlsOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Extra options related to the mongocryptd process
|
||||
* \* _Available in MongoDB 6.0 or higher._
|
||||
*/
|
||||
export type AutoEncryptionExtraOptions = NonNullable<AutoEncryptionOptions['extraOptions']>;
|
||||
|
||||
/** @public */
|
||||
export const AutoEncryptionLoggerLevel = Object.freeze({
|
||||
FatalError: 0,
|
||||
Error: 1,
|
||||
Warning: 2,
|
||||
Info: 3,
|
||||
Trace: 4
|
||||
} as const);
|
||||
|
||||
/**
|
||||
* @public
|
||||
* The level of severity of the log message
|
||||
*
|
||||
* | Value | Level |
|
||||
* |-------|-------|
|
||||
* | 0 | Fatal Error |
|
||||
* | 1 | Error |
|
||||
* | 2 | Warning |
|
||||
* | 3 | Info |
|
||||
* | 4 | Trace |
|
||||
*/
|
||||
export type AutoEncryptionLoggerLevel =
|
||||
(typeof AutoEncryptionLoggerLevel)[keyof typeof AutoEncryptionLoggerLevel];
|
||||
|
||||
/**
|
||||
* @internal An internal class to be used by the driver for auto encryption
|
||||
* **NOTE**: Not meant to be instantiated directly, this is for internal use only.
|
||||
*/
|
||||
export class AutoEncrypter {
|
||||
_client: MongoClient;
|
||||
_bypassEncryption: boolean;
|
||||
_keyVaultNamespace: string;
|
||||
_keyVaultClient: MongoClient;
|
||||
_metaDataClient: MongoClient;
|
||||
_proxyOptions: ProxyOptions;
|
||||
_tlsOptions: CSFLEKMSTlsOptions;
|
||||
_kmsProviders: KMSProviders;
|
||||
_bypassMongocryptdAndCryptShared: boolean;
|
||||
_contextCounter: number;
|
||||
_credentialProviders?: CredentialProviders;
|
||||
|
||||
_mongocryptdManager?: MongocryptdManager;
|
||||
_mongocryptdClient?: MongoClient;
|
||||
|
||||
/** @internal */
|
||||
_mongocrypt: MongoCrypt;
|
||||
|
||||
/**
|
||||
* Used by devtools to enable decorating decryption results.
|
||||
*
|
||||
* When set and enabled, `decrypt` will automatically recursively
|
||||
* traverse a decrypted document and if a field has been decrypted,
|
||||
* it will mark it as decrypted. Compass uses this to determine which
|
||||
* fields were decrypted.
|
||||
*/
|
||||
[kDecorateResult] = false;
|
||||
|
||||
/** @internal */
|
||||
static getMongoCrypt(): MongoCryptConstructor {
|
||||
const encryption = getMongoDBClientEncryption();
|
||||
if ('kModuleError' in encryption) {
|
||||
throw encryption.kModuleError;
|
||||
}
|
||||
return encryption.MongoCrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an AutoEncrypter
|
||||
*
|
||||
* **Note**: Do not instantiate this class directly. Rather, supply the relevant options to a MongoClient
|
||||
*
|
||||
* **Note**: Supplying `options.schemaMap` provides more security than relying on JSON Schemas obtained from the server.
|
||||
* It protects against a malicious server advertising a false JSON Schema, which could trick the client into sending unencrypted data that should be encrypted.
|
||||
* Schemas supplied in the schemaMap only apply to configuring automatic encryption for Client-Side Field Level Encryption.
|
||||
* Other validation rules in the JSON schema will not be enforced by the driver and will result in an error.
|
||||
*
|
||||
* @example <caption>Create an AutoEncrypter that makes use of mongocryptd</caption>
|
||||
* ```ts
|
||||
* // Enabling autoEncryption via a MongoClient using mongocryptd
|
||||
* const { MongoClient } = require('mongodb');
|
||||
* const client = new MongoClient(URL, {
|
||||
* autoEncryption: {
|
||||
* kmsProviders: {
|
||||
* aws: {
|
||||
* accessKeyId: AWS_ACCESS_KEY,
|
||||
* secretAccessKey: AWS_SECRET_KEY
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* await client.connect();
|
||||
* // From here on, the client will be encrypting / decrypting automatically
|
||||
* @example <caption>Create an AutoEncrypter that makes use of libmongocrypt's CSFLE shared library</caption>
|
||||
* ```ts
|
||||
* // Enabling autoEncryption via a MongoClient using CSFLE shared library
|
||||
* const { MongoClient } = require('mongodb');
|
||||
* const client = new MongoClient(URL, {
|
||||
* autoEncryption: {
|
||||
* kmsProviders: {
|
||||
* aws: {}
|
||||
* },
|
||||
* extraOptions: {
|
||||
* cryptSharedLibPath: '/path/to/local/crypt/shared/lib',
|
||||
* cryptSharedLibRequired: true
|
||||
* }
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* await client.connect();
|
||||
* // From here on, the client will be encrypting / decrypting automatically
|
||||
*/
|
||||
constructor(client: MongoClient, options: AutoEncryptionOptions) {
|
||||
this._client = client;
|
||||
this._bypassEncryption = options.bypassAutoEncryption === true;
|
||||
|
||||
this._keyVaultNamespace = options.keyVaultNamespace || 'admin.datakeys';
|
||||
this._keyVaultClient = options.keyVaultClient || client;
|
||||
this._metaDataClient = options.metadataClient || client;
|
||||
this._proxyOptions = options.proxyOptions || {};
|
||||
this._tlsOptions = options.tlsOptions || {};
|
||||
this._kmsProviders = options.kmsProviders || {};
|
||||
this._credentialProviders = options.credentialProviders;
|
||||
|
||||
if (options.credentialProviders?.aws && !isEmptyCredentials('aws', this._kmsProviders)) {
|
||||
throw new MongoCryptInvalidArgumentError(
|
||||
'Can only provide a custom AWS credential provider when the state machine is configured for automatic AWS credential fetching'
|
||||
);
|
||||
}
|
||||
|
||||
const mongoCryptOptions: MongoCryptOptions = {
|
||||
enableMultipleCollinfo: true,
|
||||
cryptoCallbacks
|
||||
};
|
||||
if (options.schemaMap) {
|
||||
mongoCryptOptions.schemaMap = Buffer.isBuffer(options.schemaMap)
|
||||
? options.schemaMap
|
||||
: (serialize(options.schemaMap) as Buffer);
|
||||
}
|
||||
|
||||
if (options.encryptedFieldsMap) {
|
||||
mongoCryptOptions.encryptedFieldsMap = Buffer.isBuffer(options.encryptedFieldsMap)
|
||||
? options.encryptedFieldsMap
|
||||
: (serialize(options.encryptedFieldsMap) as Buffer);
|
||||
}
|
||||
|
||||
mongoCryptOptions.kmsProviders = !Buffer.isBuffer(this._kmsProviders)
|
||||
? (serialize(this._kmsProviders) as Buffer)
|
||||
: this._kmsProviders;
|
||||
|
||||
if (options.options?.logger) {
|
||||
mongoCryptOptions.logger = options.options.logger;
|
||||
}
|
||||
|
||||
if (options.extraOptions && options.extraOptions.cryptSharedLibPath) {
|
||||
mongoCryptOptions.cryptSharedLibPath = options.extraOptions.cryptSharedLibPath;
|
||||
}
|
||||
|
||||
if (options.bypassQueryAnalysis) {
|
||||
mongoCryptOptions.bypassQueryAnalysis = options.bypassQueryAnalysis;
|
||||
}
|
||||
|
||||
this._bypassMongocryptdAndCryptShared = this._bypassEncryption || !!options.bypassQueryAnalysis;
|
||||
|
||||
if (options.extraOptions && options.extraOptions.cryptSharedLibSearchPaths) {
|
||||
// Only for driver testing
|
||||
mongoCryptOptions.cryptSharedLibSearchPaths = options.extraOptions.cryptSharedLibSearchPaths;
|
||||
} else if (!this._bypassMongocryptdAndCryptShared) {
|
||||
mongoCryptOptions.cryptSharedLibSearchPaths = ['$SYSTEM'];
|
||||
}
|
||||
|
||||
const MongoCrypt = AutoEncrypter.getMongoCrypt();
|
||||
this._mongocrypt = new MongoCrypt(mongoCryptOptions);
|
||||
this._contextCounter = 0;
|
||||
|
||||
if (
|
||||
options.extraOptions &&
|
||||
options.extraOptions.cryptSharedLibRequired &&
|
||||
!this.cryptSharedLibVersionInfo
|
||||
) {
|
||||
throw new MongoCryptInvalidArgumentError(
|
||||
'`cryptSharedLibRequired` set but no crypt_shared library loaded'
|
||||
);
|
||||
}
|
||||
|
||||
// Only instantiate mongocryptd manager/client once we know for sure
|
||||
// that we are not using the CSFLE shared library.
|
||||
if (!this._bypassMongocryptdAndCryptShared && !this.cryptSharedLibVersionInfo) {
|
||||
this._mongocryptdManager = new MongocryptdManager(options.extraOptions);
|
||||
const clientOptions: MongoClientOptions = {
|
||||
serverSelectionTimeoutMS: 10000
|
||||
};
|
||||
|
||||
if (
|
||||
(options.extraOptions == null || typeof options.extraOptions.mongocryptdURI !== 'string') &&
|
||||
!net.getDefaultAutoSelectFamily
|
||||
) {
|
||||
// Only set family if autoSelectFamily options are not supported.
|
||||
clientOptions.family = 4;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: TS complains as this always returns true on versions where it is present.
|
||||
if (net.getDefaultAutoSelectFamily) {
|
||||
// AutoEncrypter is made inside of MongoClient constructor while options are being parsed,
|
||||
// we do not have access to the options that are in progress.
|
||||
// TODO(NODE-6449): AutoEncrypter does not use client options for autoSelectFamily
|
||||
Object.assign(clientOptions, autoSelectSocketOptions(this._client.s?.options ?? {}));
|
||||
}
|
||||
|
||||
this._mongocryptdClient = new MongoClient(this._mongocryptdManager.uri, clientOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the auto encrypter by spawning a mongocryptd and connecting to it.
|
||||
*
|
||||
* This function is a no-op when bypassSpawn is set or the crypt shared library is used.
|
||||
*/
|
||||
async init(): Promise<MongoClient | void> {
|
||||
if (this._bypassMongocryptdAndCryptShared || this.cryptSharedLibVersionInfo) {
|
||||
return;
|
||||
}
|
||||
if (!this._mongocryptdManager) {
|
||||
throw new MongoRuntimeError(
|
||||
'Reached impossible state: mongocryptdManager is undefined when neither bypassSpawn nor the shared lib are specified.'
|
||||
);
|
||||
}
|
||||
if (!this._mongocryptdClient) {
|
||||
throw new MongoRuntimeError(
|
||||
'Reached impossible state: mongocryptdClient is undefined when neither bypassSpawn nor the shared lib are specified.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!this._mongocryptdManager.bypassSpawn) {
|
||||
await this._mongocryptdManager.spawn();
|
||||
}
|
||||
|
||||
try {
|
||||
const client = await this._mongocryptdClient.connect();
|
||||
return client;
|
||||
} catch (error) {
|
||||
throw new MongoRuntimeError(
|
||||
'Unable to connect to `mongocryptd`, please make sure it is running or in your PATH for auto-spawn',
|
||||
{ cause: error }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the `_mongocryptdClient`, if present.
|
||||
*/
|
||||
async teardown(force: boolean): Promise<void> {
|
||||
await this._mongocryptdClient?.close(force);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a command for a given namespace.
|
||||
*/
|
||||
async encrypt(
|
||||
ns: string,
|
||||
cmd: Document,
|
||||
options: CommandOptions & Abortable = {}
|
||||
): Promise<Document | Uint8Array> {
|
||||
options.signal?.throwIfAborted();
|
||||
|
||||
if (this._bypassEncryption) {
|
||||
// If `bypassAutoEncryption` has been specified, don't encrypt
|
||||
return cmd;
|
||||
}
|
||||
|
||||
const commandBuffer = Buffer.isBuffer(cmd) ? cmd : serialize(cmd, options);
|
||||
|
||||
const context = this._mongocrypt.makeEncryptionContext(
|
||||
MongoDBCollectionNamespace.fromString(ns).db,
|
||||
commandBuffer
|
||||
);
|
||||
|
||||
context.id = this._contextCounter++;
|
||||
context.ns = ns;
|
||||
context.document = cmd;
|
||||
|
||||
const stateMachine = new StateMachine({
|
||||
promoteValues: false,
|
||||
promoteLongs: false,
|
||||
proxyOptions: this._proxyOptions,
|
||||
tlsOptions: this._tlsOptions,
|
||||
socketOptions: autoSelectSocketOptions(this._client.s.options)
|
||||
});
|
||||
|
||||
return deserialize(await stateMachine.execute(this, context, options), {
|
||||
promoteValues: false,
|
||||
promoteLongs: false
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a command response
|
||||
*/
|
||||
async decrypt(
|
||||
response: Uint8Array,
|
||||
options: CommandOptions & Abortable = {}
|
||||
): Promise<Uint8Array> {
|
||||
options.signal?.throwIfAborted();
|
||||
|
||||
const context = this._mongocrypt.makeDecryptionContext(response);
|
||||
|
||||
context.id = this._contextCounter++;
|
||||
|
||||
const stateMachine = new StateMachine({
|
||||
...options,
|
||||
proxyOptions: this._proxyOptions,
|
||||
tlsOptions: this._tlsOptions,
|
||||
socketOptions: autoSelectSocketOptions(this._client.s.options)
|
||||
});
|
||||
|
||||
return await stateMachine.execute(this, context, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the user for KMS credentials.
|
||||
*
|
||||
* This returns anything that looks like the kmsProviders original input
|
||||
* option. It can be empty, and any provider specified here will override
|
||||
* the original ones.
|
||||
*/
|
||||
async askForKMSCredentials(): Promise<KMSProviders> {
|
||||
return await refreshKMSCredentials(this._kmsProviders, this._credentialProviders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current libmongocrypt's CSFLE shared library version
|
||||
* as `{ version: bigint, versionStr: string }`, or `null` if no CSFLE
|
||||
* shared library was loaded.
|
||||
*/
|
||||
get cryptSharedLibVersionInfo(): { version: bigint; versionStr: string } | null {
|
||||
return this._mongocrypt.cryptSharedLibVersionInfo;
|
||||
}
|
||||
|
||||
static get libmongocryptVersion(): string {
|
||||
return AutoEncrypter.getMongoCrypt().libmongocryptVersion;
|
||||
}
|
||||
}
|
||||
1097
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/client_encryption.ts
generated
vendored
Normal file
1097
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/client_encryption.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
87
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/crypto_callbacks.ts
generated
vendored
Normal file
87
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/crypto_callbacks.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import * as crypto from 'crypto';
|
||||
|
||||
type AES256Callback = (key: Buffer, iv: Buffer, input: Buffer, output: Buffer) => number | Error;
|
||||
|
||||
export function makeAES256Hook(
|
||||
method: 'createCipheriv' | 'createDecipheriv',
|
||||
mode: 'aes-256-cbc' | 'aes-256-ctr'
|
||||
): AES256Callback {
|
||||
return function (key: Buffer, iv: Buffer, input: Buffer, output: Buffer): number | Error {
|
||||
let result;
|
||||
|
||||
try {
|
||||
const cipher = crypto[method](mode, key, iv);
|
||||
cipher.setAutoPadding(false);
|
||||
result = cipher.update(input);
|
||||
const final = cipher.final();
|
||||
if (final.length > 0) {
|
||||
result = Buffer.concat([result, final]);
|
||||
}
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
|
||||
result.copy(output);
|
||||
return result.length;
|
||||
};
|
||||
}
|
||||
|
||||
export function randomHook(buffer: Buffer, count: number): number | Error {
|
||||
try {
|
||||
crypto.randomFillSync(buffer, 0, count);
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
export function sha256Hook(input: Buffer, output: Buffer): number | Error {
|
||||
let result;
|
||||
try {
|
||||
result = crypto.createHash('sha256').update(input).digest();
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
|
||||
result.copy(output);
|
||||
return result.length;
|
||||
}
|
||||
|
||||
type HMACHook = (key: Buffer, input: Buffer, output: Buffer) => number | Error;
|
||||
export function makeHmacHook(algorithm: 'sha512' | 'sha256'): HMACHook {
|
||||
return (key: Buffer, input: Buffer, output: Buffer): number | Error => {
|
||||
let result;
|
||||
try {
|
||||
result = crypto.createHmac(algorithm, key).update(input).digest();
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
|
||||
result.copy(output);
|
||||
return result.length;
|
||||
};
|
||||
}
|
||||
|
||||
export function signRsaSha256Hook(key: Buffer, input: Buffer, output: Buffer): number | Error {
|
||||
let result;
|
||||
try {
|
||||
const signer = crypto.createSign('sha256WithRSAEncryption');
|
||||
const privateKey = Buffer.from(
|
||||
`-----BEGIN PRIVATE KEY-----\n${key.toString('base64')}\n-----END PRIVATE KEY-----\n`
|
||||
);
|
||||
|
||||
result = signer.update(input).end().sign(privateKey);
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
|
||||
result.copy(output);
|
||||
return result.length;
|
||||
}
|
||||
|
||||
export const aes256CbcEncryptHook = makeAES256Hook('createCipheriv', 'aes-256-cbc');
|
||||
export const aes256CbcDecryptHook = makeAES256Hook('createDecipheriv', 'aes-256-cbc');
|
||||
export const aes256CtrEncryptHook = makeAES256Hook('createCipheriv', 'aes-256-ctr');
|
||||
export const aes256CtrDecryptHook = makeAES256Hook('createDecipheriv', 'aes-256-ctr');
|
||||
export const hmacSha512Hook = makeHmacHook('sha512');
|
||||
export const hmacSha256Hook = makeHmacHook('sha256');
|
||||
141
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/errors.ts
generated
vendored
Normal file
141
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/errors.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import { type Document } from '../bson';
|
||||
import { MongoError } from '../error';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* An error indicating that something went wrong specifically with MongoDB Client Encryption
|
||||
*/
|
||||
export class MongoCryptError extends MongoError {
|
||||
/**
|
||||
* **Do not use this constructor!**
|
||||
*
|
||||
* Meant for internal use only.
|
||||
*
|
||||
* @remarks
|
||||
* This class is only meant to be constructed within the driver. This constructor is
|
||||
* not subject to semantic versioning compatibility guarantees and may change at any time.
|
||||
*
|
||||
* @public
|
||||
**/
|
||||
constructor(message: string, options: { cause?: Error } = {}) {
|
||||
super(message, options);
|
||||
}
|
||||
|
||||
override get name() {
|
||||
return 'MongoCryptError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* An error indicating an invalid argument was provided to an encryption API.
|
||||
*/
|
||||
export class MongoCryptInvalidArgumentError extends MongoCryptError {
|
||||
/**
|
||||
* **Do not use this constructor!**
|
||||
*
|
||||
* Meant for internal use only.
|
||||
*
|
||||
* @remarks
|
||||
* This class is only meant to be constructed within the driver. This constructor is
|
||||
* not subject to semantic versioning compatibility guarantees and may change at any time.
|
||||
*
|
||||
* @public
|
||||
**/
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
override get name() {
|
||||
return 'MongoCryptInvalidArgumentError';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
* An error indicating that `ClientEncryption.createEncryptedCollection()` failed to create data keys
|
||||
*/
|
||||
export class MongoCryptCreateDataKeyError extends MongoCryptError {
|
||||
encryptedFields: Document;
|
||||
/**
|
||||
* **Do not use this constructor!**
|
||||
*
|
||||
* Meant for internal use only.
|
||||
*
|
||||
* @remarks
|
||||
* This class is only meant to be constructed within the driver. This constructor is
|
||||
* not subject to semantic versioning compatibility guarantees and may change at any time.
|
||||
*
|
||||
* @public
|
||||
**/
|
||||
constructor(encryptedFields: Document, { cause }: { cause: Error }) {
|
||||
super(`Unable to complete creating data keys: ${cause.message}`, { cause });
|
||||
this.encryptedFields = encryptedFields;
|
||||
}
|
||||
|
||||
override get name() {
|
||||
return 'MongoCryptCreateDataKeyError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* An error indicating that `ClientEncryption.createEncryptedCollection()` failed to create a collection
|
||||
*/
|
||||
export class MongoCryptCreateEncryptedCollectionError extends MongoCryptError {
|
||||
encryptedFields: Document;
|
||||
/**
|
||||
* **Do not use this constructor!**
|
||||
*
|
||||
* Meant for internal use only.
|
||||
*
|
||||
* @remarks
|
||||
* This class is only meant to be constructed within the driver. This constructor is
|
||||
* not subject to semantic versioning compatibility guarantees and may change at any time.
|
||||
*
|
||||
* @public
|
||||
**/
|
||||
constructor(encryptedFields: Document, { cause }: { cause: Error }) {
|
||||
super(`Unable to create collection: ${cause.message}`, { cause });
|
||||
this.encryptedFields = encryptedFields;
|
||||
}
|
||||
|
||||
override get name() {
|
||||
return 'MongoCryptCreateEncryptedCollectionError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* An error indicating that mongodb-client-encryption failed to auto-refresh Azure KMS credentials.
|
||||
*/
|
||||
export class MongoCryptAzureKMSRequestError extends MongoCryptError {
|
||||
/** The body of the http response that failed, if present. */
|
||||
body?: Document;
|
||||
/**
|
||||
* **Do not use this constructor!**
|
||||
*
|
||||
* Meant for internal use only.
|
||||
*
|
||||
* @remarks
|
||||
* This class is only meant to be constructed within the driver. This constructor is
|
||||
* not subject to semantic versioning compatibility guarantees and may change at any time.
|
||||
*
|
||||
* @public
|
||||
**/
|
||||
constructor(message: string, body?: Document) {
|
||||
super(message);
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
override get name(): string {
|
||||
return 'MongoCryptAzureKMSRequestError';
|
||||
}
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export class MongoCryptKMSRequestNetworkTimeoutError extends MongoCryptError {
|
||||
override get name(): string {
|
||||
return 'MongoCryptKMSRequestNetworkTimeoutError';
|
||||
}
|
||||
}
|
||||
100
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/mongocryptd_manager.ts
generated
vendored
Normal file
100
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/mongocryptd_manager.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import type { ChildProcess } from 'child_process';
|
||||
|
||||
import { MongoNetworkTimeoutError } from '../error';
|
||||
import { type AutoEncryptionExtraOptions } from './auto_encrypter';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* An internal class that handles spawning a mongocryptd.
|
||||
*/
|
||||
export class MongocryptdManager {
|
||||
static DEFAULT_MONGOCRYPTD_URI = 'mongodb://localhost:27020';
|
||||
|
||||
uri: string;
|
||||
bypassSpawn: boolean;
|
||||
spawnPath = '';
|
||||
spawnArgs: Array<string> = [];
|
||||
_child?: ChildProcess;
|
||||
|
||||
constructor(extraOptions: AutoEncryptionExtraOptions = {}) {
|
||||
this.uri =
|
||||
typeof extraOptions.mongocryptdURI === 'string' && extraOptions.mongocryptdURI.length > 0
|
||||
? extraOptions.mongocryptdURI
|
||||
: MongocryptdManager.DEFAULT_MONGOCRYPTD_URI;
|
||||
|
||||
this.bypassSpawn = !!extraOptions.mongocryptdBypassSpawn;
|
||||
|
||||
if (Object.hasOwn(extraOptions, 'mongocryptdSpawnPath') && extraOptions.mongocryptdSpawnPath) {
|
||||
this.spawnPath = extraOptions.mongocryptdSpawnPath;
|
||||
}
|
||||
if (
|
||||
Object.hasOwn(extraOptions, 'mongocryptdSpawnArgs') &&
|
||||
Array.isArray(extraOptions.mongocryptdSpawnArgs)
|
||||
) {
|
||||
this.spawnArgs = this.spawnArgs.concat(extraOptions.mongocryptdSpawnArgs);
|
||||
}
|
||||
if (
|
||||
this.spawnArgs
|
||||
.filter(arg => typeof arg === 'string')
|
||||
.every(arg => arg.indexOf('--idleShutdownTimeoutSecs') < 0)
|
||||
) {
|
||||
this.spawnArgs.push('--idleShutdownTimeoutSecs', '60');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will check to see if a mongocryptd is up. If it is not up, it will attempt
|
||||
* to spawn a mongocryptd in a detached process, and then wait for it to be up.
|
||||
*/
|
||||
async spawn(): Promise<void> {
|
||||
const cmdName = this.spawnPath || 'mongocryptd';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const { spawn } = require('child_process') as typeof import('child_process');
|
||||
|
||||
// Spawned with stdio: ignore and detached: true
|
||||
// to ensure child can outlive parent.
|
||||
this._child = spawn(cmdName, this.spawnArgs, {
|
||||
stdio: 'ignore',
|
||||
detached: true
|
||||
});
|
||||
|
||||
this._child.on('error', () => {
|
||||
// From the FLE spec:
|
||||
// "The stdout and stderr of the spawned process MUST not be exposed in the driver
|
||||
// (e.g. redirect to /dev/null). Users can pass the argument --logpath to
|
||||
// extraOptions.mongocryptdSpawnArgs if they need to inspect mongocryptd logs.
|
||||
// If spawning is necessary, the driver MUST spawn mongocryptd whenever server
|
||||
// selection on the MongoClient to mongocryptd fails. If the MongoClient fails to
|
||||
// connect after spawning, the server selection error is propagated to the user."
|
||||
// The AutoEncrypter and MongoCryptdManager should work together to spawn
|
||||
// mongocryptd whenever necessary. Additionally, the `mongocryptd` intentionally
|
||||
// shuts down after 60s and gets respawned when necessary. We rely on server
|
||||
// selection timeouts when connecting to the `mongocryptd` to inform users that something
|
||||
// has been configured incorrectly. For those reasons, we suppress stderr from
|
||||
// the `mongocryptd` process and immediately unref the process.
|
||||
});
|
||||
|
||||
// unref child to remove handle from event loop
|
||||
this._child.unref();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the result of `fn` or rejects with an error.
|
||||
*/
|
||||
async withRespawn<T>(fn: () => Promise<T>): ReturnType<typeof fn> {
|
||||
try {
|
||||
const result = await fn();
|
||||
return result;
|
||||
} catch (err) {
|
||||
// If we are not bypassing spawning, then we should retry once on a MongoTimeoutError (server selection error)
|
||||
const shouldSpawn = err instanceof MongoNetworkTimeoutError && !this.bypassSpawn;
|
||||
if (!shouldSpawn) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
await this.spawn();
|
||||
const result = await fn();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
33
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/providers/aws.ts
generated
vendored
Normal file
33
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/providers/aws.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import {
|
||||
type AWSCredentialProvider,
|
||||
AWSSDKCredentialProvider
|
||||
} from '../../cmap/auth/aws_temporary_credentials';
|
||||
import { type KMSProviders } from '.';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function loadAWSCredentials(
|
||||
kmsProviders: KMSProviders,
|
||||
provider?: AWSCredentialProvider
|
||||
): Promise<KMSProviders> {
|
||||
const credentialProvider = new AWSSDKCredentialProvider(provider);
|
||||
|
||||
// We shouldn't ever receive a response from the AWS SDK that doesn't have a `SecretAccessKey`
|
||||
// or `AccessKeyId`. However, TS says these fields are optional. We provide empty strings
|
||||
// and let libmongocrypt error if we're unable to fetch the required keys.
|
||||
const {
|
||||
SecretAccessKey = '',
|
||||
AccessKeyId = '',
|
||||
Token
|
||||
} = await credentialProvider.getCredentials();
|
||||
const aws: NonNullable<KMSProviders['aws']> = {
|
||||
secretAccessKey: SecretAccessKey,
|
||||
accessKeyId: AccessKeyId
|
||||
};
|
||||
// the AWS session token is only required for temporary credentials so only attach it to the
|
||||
// result if it's present in the response from the aws sdk
|
||||
Token != null && (aws.sessionToken = Token);
|
||||
|
||||
return { ...kmsProviders, aws };
|
||||
}
|
||||
181
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/providers/azure.ts
generated
vendored
Normal file
181
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/providers/azure.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import { type Document } from '../../bson';
|
||||
import { MongoNetworkTimeoutError } from '../../error';
|
||||
import { get } from '../../utils';
|
||||
import { MongoCryptAzureKMSRequestError } from '../errors';
|
||||
import { type KMSProviders } from './index';
|
||||
|
||||
const MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS = 6000;
|
||||
/** Base URL for getting Azure tokens. */
|
||||
export const AZURE_BASE_URL = 'http://169.254.169.254/metadata/identity/oauth2/token?';
|
||||
|
||||
/**
|
||||
* The access token that libmongocrypt expects for Azure kms.
|
||||
*/
|
||||
interface AccessToken {
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The response from the azure idms endpoint, including the `expiresOnTimestamp`.
|
||||
* `expiresOnTimestamp` is needed for caching.
|
||||
*/
|
||||
interface AzureTokenCacheEntry extends AccessToken {
|
||||
accessToken: string;
|
||||
expiresOnTimestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class AzureCredentialCache {
|
||||
cachedToken: AzureTokenCacheEntry | null = null;
|
||||
|
||||
async getToken(): Promise<AccessToken> {
|
||||
if (this.cachedToken == null || this.needsRefresh(this.cachedToken)) {
|
||||
this.cachedToken = await this._getToken();
|
||||
}
|
||||
|
||||
return { accessToken: this.cachedToken.accessToken };
|
||||
}
|
||||
|
||||
needsRefresh(token: AzureTokenCacheEntry): boolean {
|
||||
const timeUntilExpirationMS = token.expiresOnTimestamp - Date.now();
|
||||
return timeUntilExpirationMS <= MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS;
|
||||
}
|
||||
|
||||
/**
|
||||
* exposed for testing
|
||||
*/
|
||||
resetCache() {
|
||||
this.cachedToken = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* exposed for testing
|
||||
*/
|
||||
_getToken(): Promise<AzureTokenCacheEntry> {
|
||||
return fetchAzureKMSToken();
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export const tokenCache = new AzureCredentialCache();
|
||||
|
||||
/** @internal */
|
||||
async function parseResponse(response: {
|
||||
body: string;
|
||||
status?: number;
|
||||
}): Promise<AzureTokenCacheEntry> {
|
||||
const { status, body: rawBody } = response;
|
||||
|
||||
const body: { expires_in?: number; access_token?: string } = (() => {
|
||||
try {
|
||||
return JSON.parse(rawBody);
|
||||
} catch {
|
||||
throw new MongoCryptAzureKMSRequestError('Malformed JSON body in GET request.');
|
||||
}
|
||||
})();
|
||||
|
||||
if (status !== 200) {
|
||||
throw new MongoCryptAzureKMSRequestError('Unable to complete request.', body);
|
||||
}
|
||||
|
||||
if (!body.access_token) {
|
||||
throw new MongoCryptAzureKMSRequestError(
|
||||
'Malformed response body - missing field `access_token`.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!body.expires_in) {
|
||||
throw new MongoCryptAzureKMSRequestError(
|
||||
'Malformed response body - missing field `expires_in`.'
|
||||
);
|
||||
}
|
||||
|
||||
const expiresInMS = Number(body.expires_in) * 1000;
|
||||
if (Number.isNaN(expiresInMS)) {
|
||||
throw new MongoCryptAzureKMSRequestError(
|
||||
'Malformed response body - unable to parse int from `expires_in` field.'
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
accessToken: body.access_token,
|
||||
expiresOnTimestamp: Date.now() + expiresInMS
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* exposed for CSFLE
|
||||
* [prose test 18](https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#azure-imds-credentials)
|
||||
*/
|
||||
export interface AzureKMSRequestOptions {
|
||||
headers?: Document;
|
||||
url?: URL | string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Get the Azure endpoint URL.
|
||||
*/
|
||||
export function addAzureParams(url: URL, resource: string, username?: string): URL {
|
||||
url.searchParams.append('api-version', '2018-02-01');
|
||||
url.searchParams.append('resource', resource);
|
||||
if (username) {
|
||||
url.searchParams.append('client_id', username);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* parses any options provided by prose tests to `fetchAzureKMSToken` and merges them with
|
||||
* the default values for headers and the request url.
|
||||
*/
|
||||
export function prepareRequest(options: AzureKMSRequestOptions): {
|
||||
headers: Document;
|
||||
url: URL;
|
||||
} {
|
||||
const url = new URL(options.url?.toString() ?? AZURE_BASE_URL);
|
||||
addAzureParams(url, 'https://vault.azure.net');
|
||||
const headers = { ...options.headers, 'Content-Type': 'application/json', Metadata: true };
|
||||
return { headers, url };
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* `AzureKMSRequestOptions` allows prose tests to modify the http request sent to the idms
|
||||
* servers. This is required to simulate different server conditions. No options are expected to
|
||||
* be set outside of tests.
|
||||
*
|
||||
* exposed for CSFLE
|
||||
* [prose test 18](https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#azure-imds-credentials)
|
||||
*/
|
||||
export async function fetchAzureKMSToken(
|
||||
options: AzureKMSRequestOptions = {}
|
||||
): Promise<AzureTokenCacheEntry> {
|
||||
const { headers, url } = prepareRequest(options);
|
||||
try {
|
||||
const response = await get(url, { headers });
|
||||
return await parseResponse(response);
|
||||
} catch (error) {
|
||||
if (error instanceof MongoNetworkTimeoutError) {
|
||||
throw new MongoCryptAzureKMSRequestError(`[Azure KMS] ${error.message}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @throws Will reject with a `MongoCryptError` if the http request fails or the http response is malformed.
|
||||
*/
|
||||
export async function loadAzureCredentials(kmsProviders: KMSProviders): Promise<KMSProviders> {
|
||||
const azure = await tokenCache.getToken();
|
||||
return { ...kmsProviders, azure };
|
||||
}
|
||||
16
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/providers/gcp.ts
generated
vendored
Normal file
16
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/providers/gcp.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { getGcpMetadata } from '../../deps';
|
||||
import { type KMSProviders } from '.';
|
||||
|
||||
/** @internal */
|
||||
export async function loadGCPCredentials(kmsProviders: KMSProviders): Promise<KMSProviders> {
|
||||
const gcpMetadata = getGcpMetadata();
|
||||
|
||||
if ('kModuleError' in gcpMetadata) {
|
||||
return kmsProviders;
|
||||
}
|
||||
|
||||
const { access_token: accessToken } = await gcpMetadata.instance<{ access_token: string }>({
|
||||
property: 'service-accounts/default/token'
|
||||
});
|
||||
return { ...kmsProviders, gcp: { accessToken } };
|
||||
}
|
||||
207
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/providers/index.ts
generated
vendored
Normal file
207
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/providers/index.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
import type { Binary } from '../../bson';
|
||||
import { type AWSCredentialProvider } from '../../cmap/auth/aws_temporary_credentials';
|
||||
import { loadAWSCredentials } from './aws';
|
||||
import { loadAzureCredentials } from './azure';
|
||||
import { loadGCPCredentials } from './gcp';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* A data key provider. Allowed values:
|
||||
*
|
||||
* - aws, gcp, local, kmip or azure
|
||||
* - (`mongodb-client-encryption>=6.0.1` only) a named key, in the form of:
|
||||
* `aws:<name>`, `gcp:<name>`, `local:<name>`, `kmip:<name>`, `azure:<name>`
|
||||
* where `name` is an alphanumeric string, underscores allowed.
|
||||
*/
|
||||
export type ClientEncryptionDataKeyProvider = keyof KMSProviders;
|
||||
|
||||
/** @public */
|
||||
export interface AWSKMSProviderConfiguration {
|
||||
/**
|
||||
* The access key used for the AWS KMS provider
|
||||
*/
|
||||
accessKeyId: string;
|
||||
|
||||
/**
|
||||
* The secret access key used for the AWS KMS provider
|
||||
*/
|
||||
secretAccessKey: string;
|
||||
|
||||
/**
|
||||
* An optional AWS session token that will be used as the
|
||||
* X-Amz-Security-Token header for AWS requests.
|
||||
*/
|
||||
sessionToken?: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface LocalKMSProviderConfiguration {
|
||||
/**
|
||||
* The master key used to encrypt/decrypt data keys.
|
||||
* A 96-byte long Buffer or base64 encoded string.
|
||||
*/
|
||||
key: Binary | Uint8Array | string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface KMIPKMSProviderConfiguration {
|
||||
/**
|
||||
* The output endpoint string.
|
||||
* The endpoint consists of a hostname and port separated by a colon.
|
||||
* E.g. "example.com:123". A port is always present.
|
||||
*/
|
||||
endpoint?: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type AzureKMSProviderConfiguration =
|
||||
| {
|
||||
/**
|
||||
* The tenant ID identifies the organization for the account
|
||||
*/
|
||||
tenantId: string;
|
||||
|
||||
/**
|
||||
* The client ID to authenticate a registered application
|
||||
*/
|
||||
clientId: string;
|
||||
|
||||
/**
|
||||
* The client secret to authenticate a registered application
|
||||
*/
|
||||
clientSecret: string;
|
||||
|
||||
/**
|
||||
* If present, a host with optional port. E.g. "example.com" or "example.com:443".
|
||||
* This is optional, and only needed if customer is using a non-commercial Azure instance
|
||||
* (e.g. a government or China account, which use different URLs).
|
||||
* Defaults to "login.microsoftonline.com"
|
||||
*/
|
||||
identityPlatformEndpoint?: string | undefined;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* If present, an access token to authenticate with Azure.
|
||||
*/
|
||||
accessToken: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type GCPKMSProviderConfiguration =
|
||||
| {
|
||||
/**
|
||||
* The service account email to authenticate
|
||||
*/
|
||||
email: string;
|
||||
|
||||
/**
|
||||
* A PKCS#8 encrypted key. This can either be a base64 string or a binary representation
|
||||
*/
|
||||
privateKey: string | Buffer;
|
||||
|
||||
/**
|
||||
* If present, a host with optional port. E.g. "example.com" or "example.com:443".
|
||||
* Defaults to "oauth2.googleapis.com"
|
||||
*/
|
||||
endpoint?: string | undefined;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* If present, an access token to authenticate with GCP.
|
||||
*/
|
||||
accessToken: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Configuration options for custom credential providers for KMS requests.
|
||||
*/
|
||||
export interface CredentialProviders {
|
||||
/* A custom AWS credential provider */
|
||||
aws?: AWSCredentialProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Configuration options that are used by specific KMS providers during key generation, encryption, and decryption.
|
||||
*
|
||||
* Named KMS providers _are not supported_ for automatic KMS credential fetching.
|
||||
*/
|
||||
export interface KMSProviders {
|
||||
/**
|
||||
* Configuration options for using 'aws' as your KMS provider
|
||||
*/
|
||||
aws?: AWSKMSProviderConfiguration | Record<string, never>;
|
||||
[key: `aws:${string}`]: AWSKMSProviderConfiguration;
|
||||
|
||||
/**
|
||||
* Configuration options for using 'local' as your KMS provider
|
||||
*/
|
||||
local?: LocalKMSProviderConfiguration;
|
||||
[key: `local:${string}`]: LocalKMSProviderConfiguration;
|
||||
|
||||
/**
|
||||
* Configuration options for using 'kmip' as your KMS provider
|
||||
*/
|
||||
kmip?: KMIPKMSProviderConfiguration;
|
||||
[key: `kmip:${string}`]: KMIPKMSProviderConfiguration;
|
||||
|
||||
/**
|
||||
* Configuration options for using 'azure' as your KMS provider
|
||||
*/
|
||||
azure?: AzureKMSProviderConfiguration | Record<string, never>;
|
||||
[key: `azure:${string}`]: AzureKMSProviderConfiguration;
|
||||
|
||||
/**
|
||||
* Configuration options for using 'gcp' as your KMS provider
|
||||
*/
|
||||
gcp?: GCPKMSProviderConfiguration | Record<string, never>;
|
||||
[key: `gcp:${string}`]: GCPKMSProviderConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto credential fetching should only occur when the provider is defined on the kmsProviders map
|
||||
* and the settings are an empty object.
|
||||
*
|
||||
* This is distinct from a nullish provider key.
|
||||
*
|
||||
* @internal - exposed for testing purposes only
|
||||
*/
|
||||
export function isEmptyCredentials(
|
||||
providerName: ClientEncryptionDataKeyProvider,
|
||||
kmsProviders: KMSProviders
|
||||
): boolean {
|
||||
const provider = kmsProviders[providerName];
|
||||
if (provider == null) {
|
||||
return false;
|
||||
}
|
||||
return typeof provider === 'object' && Object.keys(provider).length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load cloud provider credentials for the user provided KMS providers.
|
||||
* Credentials will only attempt to get loaded if they do not exist
|
||||
* and no existing credentials will get overwritten.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function refreshKMSCredentials(
|
||||
kmsProviders: KMSProviders,
|
||||
credentialProviders?: CredentialProviders
|
||||
): Promise<KMSProviders> {
|
||||
let finalKMSProviders = kmsProviders;
|
||||
|
||||
if (isEmptyCredentials('aws', kmsProviders)) {
|
||||
finalKMSProviders = await loadAWSCredentials(finalKMSProviders, credentialProviders?.aws);
|
||||
}
|
||||
|
||||
if (isEmptyCredentials('gcp', kmsProviders)) {
|
||||
finalKMSProviders = await loadGCPCredentials(finalKMSProviders);
|
||||
}
|
||||
|
||||
if (isEmptyCredentials('azure', kmsProviders)) {
|
||||
finalKMSProviders = await loadAzureCredentials(finalKMSProviders);
|
||||
}
|
||||
return finalKMSProviders;
|
||||
}
|
||||
648
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/state_machine.ts
generated
vendored
Normal file
648
parts/3/followAlong/node_modules/mongodb/src/client-side-encryption/state_machine.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,648 @@
|
|||
import * as fs from 'fs/promises';
|
||||
import { type MongoCryptContext, type MongoCryptKMSRequest } from 'mongodb-client-encryption';
|
||||
import * as net from 'net';
|
||||
import * as tls from 'tls';
|
||||
|
||||
import {
|
||||
type BSONSerializeOptions,
|
||||
deserialize,
|
||||
type Document,
|
||||
pluckBSONSerializeOptions,
|
||||
serialize
|
||||
} from '../bson';
|
||||
import { type ProxyOptions } from '../cmap/connection';
|
||||
import { CursorTimeoutContext } from '../cursor/abstract_cursor';
|
||||
import { getSocks, type SocksLib } from '../deps';
|
||||
import { MongoOperationTimeoutError } from '../error';
|
||||
import { type MongoClient, type MongoClientOptions } from '../mongo_client';
|
||||
import { type Abortable } from '../mongo_types';
|
||||
import { type CollectionInfo } from '../operations/list_collections';
|
||||
import { Timeout, type TimeoutContext, TimeoutError } from '../timeout';
|
||||
import {
|
||||
addAbortListener,
|
||||
BufferPool,
|
||||
kDispose,
|
||||
MongoDBCollectionNamespace,
|
||||
promiseWithResolvers
|
||||
} from '../utils';
|
||||
import { autoSelectSocketOptions, type DataKey } from './client_encryption';
|
||||
import { MongoCryptError } from './errors';
|
||||
import { type MongocryptdManager } from './mongocryptd_manager';
|
||||
import { type KMSProviders } from './providers';
|
||||
|
||||
let socks: SocksLib | null = null;
|
||||
function loadSocks(): SocksLib {
|
||||
if (socks == null) {
|
||||
const socksImport = getSocks();
|
||||
if ('kModuleError' in socksImport) {
|
||||
throw socksImport.kModuleError;
|
||||
}
|
||||
socks = socksImport;
|
||||
}
|
||||
return socks;
|
||||
}
|
||||
|
||||
// libmongocrypt states
|
||||
const MONGOCRYPT_CTX_ERROR = 0;
|
||||
const MONGOCRYPT_CTX_NEED_MONGO_COLLINFO = 1;
|
||||
const MONGOCRYPT_CTX_NEED_MONGO_MARKINGS = 2;
|
||||
const MONGOCRYPT_CTX_NEED_MONGO_KEYS = 3;
|
||||
const MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS = 7;
|
||||
const MONGOCRYPT_CTX_NEED_KMS = 4;
|
||||
const MONGOCRYPT_CTX_READY = 5;
|
||||
const MONGOCRYPT_CTX_DONE = 6;
|
||||
|
||||
const HTTPS_PORT = 443;
|
||||
|
||||
const stateToString = new Map([
|
||||
[MONGOCRYPT_CTX_ERROR, 'MONGOCRYPT_CTX_ERROR'],
|
||||
[MONGOCRYPT_CTX_NEED_MONGO_COLLINFO, 'MONGOCRYPT_CTX_NEED_MONGO_COLLINFO'],
|
||||
[MONGOCRYPT_CTX_NEED_MONGO_MARKINGS, 'MONGOCRYPT_CTX_NEED_MONGO_MARKINGS'],
|
||||
[MONGOCRYPT_CTX_NEED_MONGO_KEYS, 'MONGOCRYPT_CTX_NEED_MONGO_KEYS'],
|
||||
[MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS, 'MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS'],
|
||||
[MONGOCRYPT_CTX_NEED_KMS, 'MONGOCRYPT_CTX_NEED_KMS'],
|
||||
[MONGOCRYPT_CTX_READY, 'MONGOCRYPT_CTX_READY'],
|
||||
[MONGOCRYPT_CTX_DONE, 'MONGOCRYPT_CTX_DONE']
|
||||
]);
|
||||
|
||||
const INSECURE_TLS_OPTIONS = [
|
||||
'tlsInsecure',
|
||||
'tlsAllowInvalidCertificates',
|
||||
'tlsAllowInvalidHostnames',
|
||||
|
||||
// These options are disallowed by the spec, so we explicitly filter them out if provided, even
|
||||
// though the StateMachine does not declare support for these options.
|
||||
'tlsDisableOCSPEndpointCheck',
|
||||
'tlsDisableCertificateRevocationCheck'
|
||||
];
|
||||
|
||||
/**
|
||||
* Helper function for logging. Enabled by setting the environment flag MONGODB_CRYPT_DEBUG.
|
||||
* @param msg - Anything you want to be logged.
|
||||
*/
|
||||
function debug(msg: unknown) {
|
||||
if (process.env.MONGODB_CRYPT_DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'mongodb-client-encryption' {
|
||||
// the properties added to `MongoCryptContext` here are only used for the `StateMachine`'s
|
||||
// execute method and are not part of the C++ bindings.
|
||||
interface MongoCryptContext {
|
||||
id: number;
|
||||
document: Document;
|
||||
ns: string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* TLS options to use when connecting. The spec specifically calls out which insecure
|
||||
* tls options are not allowed:
|
||||
*
|
||||
* - tlsAllowInvalidCertificates
|
||||
* - tlsAllowInvalidHostnames
|
||||
* - tlsInsecure
|
||||
*
|
||||
* These options are not included in the type, and are ignored if provided.
|
||||
*/
|
||||
export type ClientEncryptionTlsOptions = Pick<
|
||||
MongoClientOptions,
|
||||
'tlsCAFile' | 'tlsCertificateKeyFile' | 'tlsCertificateKeyFilePassword'
|
||||
>;
|
||||
|
||||
/** @public */
|
||||
export type CSFLEKMSTlsOptions = {
|
||||
aws?: ClientEncryptionTlsOptions;
|
||||
gcp?: ClientEncryptionTlsOptions;
|
||||
kmip?: ClientEncryptionTlsOptions;
|
||||
local?: ClientEncryptionTlsOptions;
|
||||
azure?: ClientEncryptionTlsOptions;
|
||||
|
||||
[key: string]: ClientEncryptionTlsOptions | undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*
|
||||
* Socket options to use for KMS requests.
|
||||
*/
|
||||
export type ClientEncryptionSocketOptions = Pick<
|
||||
MongoClientOptions,
|
||||
'autoSelectFamily' | 'autoSelectFamilyAttemptTimeout'
|
||||
>;
|
||||
|
||||
/**
|
||||
* This is kind of a hack. For `rewrapManyDataKey`, we have tests that
|
||||
* guarantee that when there are no matching keys, `rewrapManyDataKey` returns
|
||||
* nothing. We also have tests for auto encryption that guarantee for `encrypt`
|
||||
* we return an error when there are no matching keys. This error is generated in
|
||||
* subsequent iterations of the state machine.
|
||||
* Some apis (`encrypt`) throw if there are no filter matches and others (`rewrapManyDataKey`)
|
||||
* do not. We set the result manually here, and let the state machine continue. `libmongocrypt`
|
||||
* will inform us if we need to error by setting the state to `MONGOCRYPT_CTX_ERROR` but
|
||||
* otherwise we'll return `{ v: [] }`.
|
||||
*/
|
||||
let EMPTY_V;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* An interface representing an object that can be passed to the `StateMachine.execute` method.
|
||||
*
|
||||
* Not all properties are required for all operations.
|
||||
*/
|
||||
export interface StateMachineExecutable {
|
||||
_keyVaultNamespace: string;
|
||||
_keyVaultClient: MongoClient;
|
||||
askForKMSCredentials: () => Promise<KMSProviders>;
|
||||
|
||||
/** only used for auto encryption */
|
||||
_metaDataClient?: MongoClient;
|
||||
/** only used for auto encryption */
|
||||
_mongocryptdClient?: MongoClient;
|
||||
/** only used for auto encryption */
|
||||
_mongocryptdManager?: MongocryptdManager;
|
||||
}
|
||||
|
||||
export type StateMachineOptions = {
|
||||
/** socks5 proxy options, if set. */
|
||||
proxyOptions: ProxyOptions;
|
||||
|
||||
/** TLS options for KMS requests, if set. */
|
||||
tlsOptions: CSFLEKMSTlsOptions;
|
||||
|
||||
/** Socket specific options we support. */
|
||||
socketOptions: ClientEncryptionSocketOptions;
|
||||
} & Pick<BSONSerializeOptions, 'promoteLongs' | 'promoteValues'>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* An internal class that executes across a MongoCryptContext until either
|
||||
* a finishing state or an error is reached. Do not instantiate directly.
|
||||
*/
|
||||
// TODO(DRIVERS-2671): clarify CSOT behavior for FLE APIs
|
||||
export class StateMachine {
|
||||
constructor(
|
||||
private options: StateMachineOptions,
|
||||
private bsonOptions = pluckBSONSerializeOptions(options)
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Executes the state machine according to the specification
|
||||
*/
|
||||
async execute(
|
||||
executor: StateMachineExecutable,
|
||||
context: MongoCryptContext,
|
||||
options: { timeoutContext?: TimeoutContext } & Abortable
|
||||
): Promise<Uint8Array> {
|
||||
const keyVaultNamespace = executor._keyVaultNamespace;
|
||||
const keyVaultClient = executor._keyVaultClient;
|
||||
const metaDataClient = executor._metaDataClient;
|
||||
const mongocryptdClient = executor._mongocryptdClient;
|
||||
const mongocryptdManager = executor._mongocryptdManager;
|
||||
let result: Uint8Array | null = null;
|
||||
|
||||
// Typescript treats getters just like properties: Once you've tested it for equality
|
||||
// it cannot change. Which is exactly the opposite of what we use state and status for.
|
||||
// Every call to at least `addMongoOperationResponse` and `finalize` can change the state.
|
||||
// These wrappers let us write code more naturally and not add compiler exceptions
|
||||
// to conditions checks inside the state machine.
|
||||
const getStatus = () => context.status;
|
||||
const getState = () => context.state;
|
||||
|
||||
while (getState() !== MONGOCRYPT_CTX_DONE && getState() !== MONGOCRYPT_CTX_ERROR) {
|
||||
options.signal?.throwIfAborted();
|
||||
debug(`[context#${context.id}] ${stateToString.get(getState()) || getState()}`);
|
||||
|
||||
switch (getState()) {
|
||||
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO: {
|
||||
const filter = deserialize(context.nextMongoOperation());
|
||||
if (!metaDataClient) {
|
||||
throw new MongoCryptError(
|
||||
'unreachable state machine state: entered MONGOCRYPT_CTX_NEED_MONGO_COLLINFO but metadata client is undefined'
|
||||
);
|
||||
}
|
||||
|
||||
const collInfoCursor = this.fetchCollectionInfo(
|
||||
metaDataClient,
|
||||
context.ns,
|
||||
filter,
|
||||
options
|
||||
);
|
||||
|
||||
for await (const collInfo of collInfoCursor) {
|
||||
context.addMongoOperationResponse(serialize(collInfo));
|
||||
if (getState() === MONGOCRYPT_CTX_ERROR) break;
|
||||
}
|
||||
|
||||
if (getState() === MONGOCRYPT_CTX_ERROR) break;
|
||||
|
||||
context.finishMongoOperation();
|
||||
break;
|
||||
}
|
||||
|
||||
case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS: {
|
||||
const command = context.nextMongoOperation();
|
||||
if (getState() === MONGOCRYPT_CTX_ERROR) break;
|
||||
|
||||
if (!mongocryptdClient) {
|
||||
throw new MongoCryptError(
|
||||
'unreachable state machine state: entered MONGOCRYPT_CTX_NEED_MONGO_MARKINGS but mongocryptdClient is undefined'
|
||||
);
|
||||
}
|
||||
|
||||
// When we are using the shared library, we don't have a mongocryptd manager.
|
||||
const markedCommand: Uint8Array = mongocryptdManager
|
||||
? await mongocryptdManager.withRespawn(
|
||||
this.markCommand.bind(this, mongocryptdClient, context.ns, command, options)
|
||||
)
|
||||
: await this.markCommand(mongocryptdClient, context.ns, command, options);
|
||||
|
||||
context.addMongoOperationResponse(markedCommand);
|
||||
context.finishMongoOperation();
|
||||
break;
|
||||
}
|
||||
|
||||
case MONGOCRYPT_CTX_NEED_MONGO_KEYS: {
|
||||
const filter = context.nextMongoOperation();
|
||||
const keys = await this.fetchKeys(keyVaultClient, keyVaultNamespace, filter, options);
|
||||
|
||||
if (keys.length === 0) {
|
||||
// See docs on EMPTY_V
|
||||
result = EMPTY_V ??= serialize({ v: [] });
|
||||
}
|
||||
for await (const key of keys) {
|
||||
context.addMongoOperationResponse(serialize(key));
|
||||
}
|
||||
|
||||
context.finishMongoOperation();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MONGOCRYPT_CTX_NEED_KMS_CREDENTIALS: {
|
||||
const kmsProviders = await executor.askForKMSCredentials();
|
||||
context.provideKMSProviders(serialize(kmsProviders));
|
||||
break;
|
||||
}
|
||||
|
||||
case MONGOCRYPT_CTX_NEED_KMS: {
|
||||
await Promise.all(this.requests(context, options));
|
||||
context.finishKMSRequests();
|
||||
break;
|
||||
}
|
||||
|
||||
case MONGOCRYPT_CTX_READY: {
|
||||
const finalizedContext = context.finalize();
|
||||
if (getState() === MONGOCRYPT_CTX_ERROR) {
|
||||
const message = getStatus().message || 'Finalization error';
|
||||
throw new MongoCryptError(message);
|
||||
}
|
||||
result = finalizedContext;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new MongoCryptError(`Unknown state: ${getState()}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (getState() === MONGOCRYPT_CTX_ERROR || result == null) {
|
||||
const message = getStatus().message;
|
||||
if (!message) {
|
||||
debug(
|
||||
`unidentifiable error in MongoCrypt - received an error status from \`libmongocrypt\` but received no error message.`
|
||||
);
|
||||
}
|
||||
throw new MongoCryptError(
|
||||
message ??
|
||||
'unidentifiable error in MongoCrypt - received an error status from `libmongocrypt` but received no error message.'
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request to the KMS service. Exposed for testing purposes. Do not directly invoke.
|
||||
* @param kmsContext - A C++ KMS context returned from the bindings
|
||||
* @returns A promise that resolves when the KMS reply has be fully parsed
|
||||
*/
|
||||
async kmsRequest(
|
||||
request: MongoCryptKMSRequest,
|
||||
options?: { timeoutContext?: TimeoutContext } & Abortable
|
||||
): Promise<void> {
|
||||
const parsedUrl = request.endpoint.split(':');
|
||||
const port = parsedUrl[1] != null ? Number.parseInt(parsedUrl[1], 10) : HTTPS_PORT;
|
||||
const socketOptions: tls.ConnectionOptions & {
|
||||
host: string;
|
||||
port: number;
|
||||
autoSelectFamily?: boolean;
|
||||
autoSelectFamilyAttemptTimeout?: number;
|
||||
} = {
|
||||
host: parsedUrl[0],
|
||||
servername: parsedUrl[0],
|
||||
port,
|
||||
...autoSelectSocketOptions(this.options.socketOptions || {})
|
||||
};
|
||||
const message = request.message;
|
||||
const buffer = new BufferPool();
|
||||
|
||||
let netSocket: net.Socket;
|
||||
let socket: tls.TLSSocket;
|
||||
|
||||
function destroySockets() {
|
||||
for (const sock of [socket, netSocket]) {
|
||||
if (sock) {
|
||||
sock.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onerror(cause: Error) {
|
||||
return new MongoCryptError('KMS request failed', { cause });
|
||||
}
|
||||
|
||||
function onclose() {
|
||||
return new MongoCryptError('KMS request closed');
|
||||
}
|
||||
|
||||
const tlsOptions = this.options.tlsOptions;
|
||||
if (tlsOptions) {
|
||||
const kmsProvider = request.kmsProvider;
|
||||
const providerTlsOptions = tlsOptions[kmsProvider];
|
||||
if (providerTlsOptions) {
|
||||
const error = this.validateTlsOptions(kmsProvider, providerTlsOptions);
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
try {
|
||||
await this.setTlsOptions(providerTlsOptions, socketOptions);
|
||||
} catch (err) {
|
||||
throw onerror(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let abortListener;
|
||||
|
||||
try {
|
||||
if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) {
|
||||
netSocket = new net.Socket();
|
||||
|
||||
const {
|
||||
promise: willConnect,
|
||||
reject: rejectOnNetSocketError,
|
||||
resolve: resolveOnNetSocketConnect
|
||||
} = promiseWithResolvers<void>();
|
||||
|
||||
netSocket
|
||||
.once('error', err => rejectOnNetSocketError(onerror(err)))
|
||||
.once('close', () => rejectOnNetSocketError(onclose()))
|
||||
.once('connect', () => resolveOnNetSocketConnect());
|
||||
|
||||
const netSocketOptions = {
|
||||
...socketOptions,
|
||||
host: this.options.proxyOptions.proxyHost,
|
||||
port: this.options.proxyOptions.proxyPort || 1080
|
||||
};
|
||||
|
||||
netSocket.connect(netSocketOptions);
|
||||
|
||||
await willConnect;
|
||||
|
||||
try {
|
||||
socks ??= loadSocks();
|
||||
socketOptions.socket = (
|
||||
await socks.SocksClient.createConnection({
|
||||
existing_socket: netSocket,
|
||||
command: 'connect',
|
||||
destination: { host: socketOptions.host, port: socketOptions.port },
|
||||
proxy: {
|
||||
// host and port are ignored because we pass existing_socket
|
||||
host: 'iLoveJavaScript',
|
||||
port: 0,
|
||||
type: 5,
|
||||
userId: this.options.proxyOptions.proxyUsername,
|
||||
password: this.options.proxyOptions.proxyPassword
|
||||
}
|
||||
})
|
||||
).socket;
|
||||
} catch (err) {
|
||||
throw onerror(err);
|
||||
}
|
||||
}
|
||||
|
||||
socket = tls.connect(socketOptions, () => {
|
||||
socket.write(message);
|
||||
});
|
||||
|
||||
const {
|
||||
promise: willResolveKmsRequest,
|
||||
reject: rejectOnTlsSocketError,
|
||||
resolve
|
||||
} = promiseWithResolvers<void>();
|
||||
|
||||
abortListener = addAbortListener(options?.signal, function () {
|
||||
destroySockets();
|
||||
rejectOnTlsSocketError(this.reason);
|
||||
});
|
||||
|
||||
socket
|
||||
.once('error', err => rejectOnTlsSocketError(onerror(err)))
|
||||
.once('close', () => rejectOnTlsSocketError(onclose()))
|
||||
.on('data', data => {
|
||||
buffer.append(data);
|
||||
while (request.bytesNeeded > 0 && buffer.length) {
|
||||
const bytesNeeded = Math.min(request.bytesNeeded, buffer.length);
|
||||
request.addResponse(buffer.read(bytesNeeded));
|
||||
}
|
||||
|
||||
if (request.bytesNeeded <= 0) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
await (options?.timeoutContext?.csotEnabled()
|
||||
? Promise.all([
|
||||
willResolveKmsRequest,
|
||||
Timeout.expires(options.timeoutContext?.remainingTimeMS)
|
||||
])
|
||||
: willResolveKmsRequest);
|
||||
} catch (error) {
|
||||
if (error instanceof TimeoutError)
|
||||
throw new MongoOperationTimeoutError('KMS request timed out');
|
||||
throw error;
|
||||
} finally {
|
||||
// There's no need for any more activity on this socket at this point.
|
||||
destroySockets();
|
||||
abortListener?.[kDispose]();
|
||||
}
|
||||
}
|
||||
|
||||
*requests(context: MongoCryptContext, options?: { timeoutContext?: TimeoutContext } & Abortable) {
|
||||
for (
|
||||
let request = context.nextKMSRequest();
|
||||
request != null;
|
||||
request = context.nextKMSRequest()
|
||||
) {
|
||||
yield this.kmsRequest(request, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the provided TLS options are secure.
|
||||
*
|
||||
* @param kmsProvider - The KMS provider name.
|
||||
* @param tlsOptions - The client TLS options for the provider.
|
||||
*
|
||||
* @returns An error if any option is invalid.
|
||||
*/
|
||||
validateTlsOptions(
|
||||
kmsProvider: string,
|
||||
tlsOptions: ClientEncryptionTlsOptions
|
||||
): MongoCryptError | void {
|
||||
const tlsOptionNames = Object.keys(tlsOptions);
|
||||
for (const option of INSECURE_TLS_OPTIONS) {
|
||||
if (tlsOptionNames.includes(option)) {
|
||||
return new MongoCryptError(`Insecure TLS options prohibited for ${kmsProvider}: ${option}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets only the valid secure TLS options.
|
||||
*
|
||||
* @param tlsOptions - The client TLS options for the provider.
|
||||
* @param options - The existing connection options.
|
||||
*/
|
||||
async setTlsOptions(
|
||||
tlsOptions: ClientEncryptionTlsOptions,
|
||||
options: tls.ConnectionOptions
|
||||
): Promise<void> {
|
||||
if (tlsOptions.tlsCertificateKeyFile) {
|
||||
const cert = await fs.readFile(tlsOptions.tlsCertificateKeyFile);
|
||||
options.cert = options.key = cert;
|
||||
}
|
||||
if (tlsOptions.tlsCAFile) {
|
||||
options.ca = await fs.readFile(tlsOptions.tlsCAFile);
|
||||
}
|
||||
if (tlsOptions.tlsCertificateKeyFilePassword) {
|
||||
options.passphrase = tlsOptions.tlsCertificateKeyFilePassword;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches collection info for a provided namespace, when libmongocrypt
|
||||
* enters the `MONGOCRYPT_CTX_NEED_MONGO_COLLINFO` state. The result is
|
||||
* used to inform libmongocrypt of the schema associated with this
|
||||
* namespace. Exposed for testing purposes. Do not directly invoke.
|
||||
*
|
||||
* @param client - A MongoClient connected to the topology
|
||||
* @param ns - The namespace to list collections from
|
||||
* @param filter - A filter for the listCollections command
|
||||
* @param callback - Invoked with the info of the requested collection, or with an error
|
||||
*/
|
||||
fetchCollectionInfo(
|
||||
client: MongoClient,
|
||||
ns: string,
|
||||
filter: Document,
|
||||
options?: { timeoutContext?: TimeoutContext } & Abortable
|
||||
): AsyncIterable<CollectionInfo> {
|
||||
const { db } = MongoDBCollectionNamespace.fromString(ns);
|
||||
|
||||
const cursor = client.db(db).listCollections(filter, {
|
||||
promoteLongs: false,
|
||||
promoteValues: false,
|
||||
timeoutContext:
|
||||
options?.timeoutContext && new CursorTimeoutContext(options?.timeoutContext, Symbol()),
|
||||
signal: options?.signal,
|
||||
nameOnly: false
|
||||
});
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls to the mongocryptd to provide markings for a command.
|
||||
* Exposed for testing purposes. Do not directly invoke.
|
||||
* @param client - A MongoClient connected to a mongocryptd
|
||||
* @param ns - The namespace (database.collection) the command is being executed on
|
||||
* @param command - The command to execute.
|
||||
* @param callback - Invoked with the serialized and marked bson command, or with an error
|
||||
*/
|
||||
async markCommand(
|
||||
client: MongoClient,
|
||||
ns: string,
|
||||
command: Uint8Array,
|
||||
options?: { timeoutContext?: TimeoutContext } & Abortable
|
||||
): Promise<Uint8Array> {
|
||||
const { db } = MongoDBCollectionNamespace.fromString(ns);
|
||||
const bsonOptions = { promoteLongs: false, promoteValues: false };
|
||||
const rawCommand = deserialize(command, bsonOptions);
|
||||
|
||||
const commandOptions: {
|
||||
timeoutMS?: number;
|
||||
signal?: AbortSignal;
|
||||
} = {
|
||||
timeoutMS: undefined,
|
||||
signal: undefined
|
||||
};
|
||||
|
||||
if (options?.timeoutContext?.csotEnabled()) {
|
||||
commandOptions.timeoutMS = options.timeoutContext.remainingTimeMS;
|
||||
}
|
||||
if (options?.signal) {
|
||||
commandOptions.signal = options.signal;
|
||||
}
|
||||
|
||||
const response = await client.db(db).command(rawCommand, {
|
||||
...bsonOptions,
|
||||
...commandOptions
|
||||
});
|
||||
|
||||
return serialize(response, this.bsonOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests keys from the keyVault collection on the topology.
|
||||
* Exposed for testing purposes. Do not directly invoke.
|
||||
* @param client - A MongoClient connected to the topology
|
||||
* @param keyVaultNamespace - The namespace (database.collection) of the keyVault Collection
|
||||
* @param filter - The filter for the find query against the keyVault Collection
|
||||
* @param callback - Invoked with the found keys, or with an error
|
||||
*/
|
||||
fetchKeys(
|
||||
client: MongoClient,
|
||||
keyVaultNamespace: string,
|
||||
filter: Uint8Array,
|
||||
options?: { timeoutContext?: TimeoutContext } & Abortable
|
||||
): Promise<Array<DataKey>> {
|
||||
const { db: dbName, collection: collectionName } =
|
||||
MongoDBCollectionNamespace.fromString(keyVaultNamespace);
|
||||
|
||||
const commandOptions: {
|
||||
timeoutContext?: CursorTimeoutContext;
|
||||
signal?: AbortSignal;
|
||||
} = {
|
||||
timeoutContext: undefined,
|
||||
signal: undefined
|
||||
};
|
||||
|
||||
if (options?.timeoutContext != null) {
|
||||
commandOptions.timeoutContext = new CursorTimeoutContext(options.timeoutContext, Symbol());
|
||||
}
|
||||
if (options?.signal != null) {
|
||||
commandOptions.signal = options.signal;
|
||||
}
|
||||
|
||||
return client
|
||||
.db(dbName)
|
||||
.collection<DataKey>(collectionName, { readConcern: { level: 'majority' } })
|
||||
.find(deserialize(filter), commandOptions)
|
||||
.toArray();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue