Spaces:
Runtime error
Runtime error
File size: 13,295 Bytes
9756872 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 | "use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AutoEncrypter = exports.AutoEncryptionLoggerLevel = void 0;
const bson_1 = require("../bson");
const deps_1 = require("../deps");
const error_1 = require("../error");
const mongo_client_1 = require("../mongo_client");
const utils_1 = require("../utils");
const cryptoCallbacks = require("./crypto_callbacks");
const errors_1 = require("./errors");
const mongocryptd_manager_1 = require("./mongocryptd_manager");
const providers_1 = require("./providers");
const state_machine_1 = require("./state_machine");
/** @public */
exports.AutoEncryptionLoggerLevel = Object.freeze({
FatalError: 0,
Error: 1,
Warning: 2,
Info: 3,
Trace: 4
});
// Typescript errors if we index objects with `Symbol.for(...)`, so
// to avoid TS errors we pull them out into variables. Then we can type
// the objects (and class) that we expect to see them on and prevent TS
// errors.
/** @internal */
const kDecorateResult = Symbol.for('@@mdb.decorateDecryptionResult');
/** @internal */
const kDecoratedKeys = Symbol.for('@@mdb.decryptedKeys');
/**
* @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.
*/
class AutoEncrypter {
/** @internal */
static getMongoCrypt() {
const encryption = (0, deps_1.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, options) {
/**
* 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.
*/
this[_a] = false;
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 || {};
const mongoCryptOptions = {
cryptoCallbacks
};
if (options.schemaMap) {
mongoCryptOptions.schemaMap = Buffer.isBuffer(options.schemaMap)
? options.schemaMap
: (0, bson_1.serialize)(options.schemaMap);
}
if (options.encryptedFieldsMap) {
mongoCryptOptions.encryptedFieldsMap = Buffer.isBuffer(options.encryptedFieldsMap)
? options.encryptedFieldsMap
: (0, bson_1.serialize)(options.encryptedFieldsMap);
}
mongoCryptOptions.kmsProviders = !Buffer.isBuffer(this._kmsProviders)
? (0, bson_1.serialize)(this._kmsProviders)
: 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 errors_1.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 mongocryptd_manager_1.MongocryptdManager(options.extraOptions);
const clientOptions = {
serverSelectionTimeoutMS: 10000
};
if (options.extraOptions == null || typeof options.extraOptions.mongocryptdURI !== 'string') {
clientOptions.family = 4;
}
this._mongocryptdClient = new mongo_client_1.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() {
if (this._bypassMongocryptdAndCryptShared || this.cryptSharedLibVersionInfo) {
return;
}
if (!this._mongocryptdManager) {
throw new error_1.MongoRuntimeError('Reached impossible state: mongocryptdManager is undefined when neither bypassSpawn nor the shared lib are specified.');
}
if (!this._mongocryptdClient) {
throw new error_1.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) {
const { message } = error;
if (message && (message.match(/timed out after/) || message.match(/ENOTFOUND/))) {
throw new error_1.MongoRuntimeError('Unable to connect to `mongocryptd`, please make sure it is running or in your PATH for auto-spawn', { cause: error });
}
throw error;
}
}
/**
* Cleans up the `_mongocryptdClient`, if present.
*/
async teardown(force) {
await this._mongocryptdClient?.close(force);
}
/**
* Encrypt a command for a given namespace.
*/
async encrypt(ns, cmd, options = {}) {
if (this._bypassEncryption) {
// If `bypassAutoEncryption` has been specified, don't encrypt
return cmd;
}
const commandBuffer = Buffer.isBuffer(cmd) ? cmd : (0, bson_1.serialize)(cmd, options);
const context = this._mongocrypt.makeEncryptionContext(utils_1.MongoDBCollectionNamespace.fromString(ns).db, commandBuffer);
context.id = this._contextCounter++;
context.ns = ns;
context.document = cmd;
const stateMachine = new state_machine_1.StateMachine({
promoteValues: false,
promoteLongs: false,
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
});
return await stateMachine.execute(this, context);
}
/**
* Decrypt a command response
*/
async decrypt(response, options = {}) {
const buffer = Buffer.isBuffer(response) ? response : (0, bson_1.serialize)(response, options);
const context = this._mongocrypt.makeDecryptionContext(buffer);
context.id = this._contextCounter++;
const stateMachine = new state_machine_1.StateMachine({
...options,
proxyOptions: this._proxyOptions,
tlsOptions: this._tlsOptions
});
const decorateResult = this[kDecorateResult];
const result = await stateMachine.execute(this, context);
if (decorateResult) {
decorateDecryptionResult(result, response);
}
return result;
}
/**
* 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() {
return await (0, providers_1.refreshKMSCredentials)(this._kmsProviders);
}
/**
* 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() {
return this._mongocrypt.cryptSharedLibVersionInfo;
}
static get libmongocryptVersion() {
return AutoEncrypter.getMongoCrypt().libmongocryptVersion;
}
}
exports.AutoEncrypter = AutoEncrypter;
_a = kDecorateResult;
/**
* Recurse through the (identically-shaped) `decrypted` and `original`
* objects and attach a `decryptedKeys` property on each sub-object that
* contained encrypted fields. Because we only call this on BSON responses,
* we do not need to worry about circular references.
*
* @internal
*/
function decorateDecryptionResult(decrypted, original, isTopLevelDecorateCall = true) {
if (isTopLevelDecorateCall) {
// The original value could have been either a JS object or a BSON buffer
if (Buffer.isBuffer(original)) {
original = (0, bson_1.deserialize)(original);
}
if (Buffer.isBuffer(decrypted)) {
throw new error_1.MongoRuntimeError('Expected result of decryption to be deserialized BSON object');
}
}
if (!decrypted || typeof decrypted !== 'object')
return;
for (const k of Object.keys(decrypted)) {
const originalValue = original[k];
// An object was decrypted by libmongocrypt if and only if it was
// a BSON Binary object with subtype 6.
if (originalValue && originalValue._bsontype === 'Binary' && originalValue.sub_type === 6) {
if (!decrypted[kDecoratedKeys]) {
Object.defineProperty(decrypted, kDecoratedKeys, {
value: [],
configurable: true,
enumerable: false,
writable: false
});
}
// this is defined in the preceding if-statement
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
decrypted[kDecoratedKeys].push(k);
// Do not recurse into this decrypted value. It could be a sub-document/array,
// in which case there is no original value associated with its subfields.
continue;
}
decorateDecryptionResult(decrypted[k], originalValue, false);
}
}
//# sourceMappingURL=auto_encrypter.js.map |