MongoDB 7.0 には、より高いデータベースセキュリティが求められるシナリオに対応するためのクエリ可能暗号化機能が搭載されています。このトピックでは、クエリ可能暗号化機能の使用方法について説明します。
背景情報
ApsaraDB for MongoDB が提供する透過的なデータ暗号化 (TDE) およびディスク暗暗号化機能は、保管時の暗号化 (encryption at rest) ソリューションです。これらは次の目的を果たします。
データ保護:ディスク上のデータを不正アクセスから保護します。悪意のあるユーザーが、データが格納されている HDD または SSD に物理的にアクセスできたとしても、暗号化されていないデータにはアクセスできません。
漏えい防止:データセンターでセキュリティインシデントが発生した場合やノート PC を紛失した場合など、ストレージデバイスが盗難または紛失した際にも、暗号化によって機密データへの不正なアクセスを防ぎます。
コンプライアンス要件:多くの業界標準および規制で、企業に機密データの暗号化が求められています。機密データには、ユーザーの個人情報や金融情報などが含まれます。保管時の暗号化ソリューションは、企業が規制要件を満たすのに役立ちます。
TDE またはディスク暗号化を有効にした ApsaraDB for MongoDB インスタンスのバックアップファイルは暗号化されます。
保管時の暗号化ソリューションを使用しても、メモリに読み込まれたデータはプレーンテキストのままです。データを完全に保護するために、Secure Sockets Layer (SSL) または Transport Layer Security (TLS) などのネットワーク暗号化、データベースのアクセス制御、監査、モニタリングなど、追加のセキュリティ対策を実装することを推奨します。データベースサービスをホストする Elastic Compute Service (ECS) インスタンスに対する Alibaba Cloud の社内 O&M 担当者によるアクセスへの懸念を解消するために、Alibaba Cloud は、セキュリティリスクを防止するための顧客承認および強制監査を提供しています。
データベースセキュリティに関してより高い要件があり、追加の暗号化方式が必要な場合は、MongoDB 7.0 で正式リリースされたクエリ可能暗号化機能を使用できます。
概要
クエリ可能暗号化機能のプレビュー版は MongoDB 6.0 で、正式版は MongoDB 7.0 でリリースされました。
クエリ可能暗号化機能を使用すると、データはクライアントに到達するまで暗号化されたままになります。クエリは、Key Management Service (KMS) で管理される暗号化キーとともにサーバーに送信されます。その後、サーバー側でデータのクエリが実行され、暗号文として返されます。データがクライアントに返された後、キーを使用して復号され、プレーンテキストとして表示されます。
クエリ可能暗暗号化機能には、次の特長があります。
クライアント側で機密データを暗号化し、暗号化キーはクライアントのみが取得できます。
転送、保管、使用、監査、バックアップを含むデータライフサイクル全体でデータを暗号化します。
等価、範囲、プレフィックス、サフィックス、部分文字列クエリなど、暗号化データに対する表現力の高いクエリをクライアントで実行できます。
データプライバシー保護を強化します。サーバー上のアプリケーションにアクセスでき、暗号化キーを使用できる承認済みユーザーのみが、データをプレーンテキストで確認できます。
機密データを扱うアプリケーションの開発を容易にします。開発者は、データベースに組み込まれた包括的な暗号化機能を直接使用して、セキュリティとコンプライアンスを確保できます。
ApsaraDB for MongoDB に機密データを保存したい Alibaba Cloud ユーザーのセキュリティ上の懸念を軽減します。
MongoDB Community Edition でリリースされる機能は、Enterprise Edition (Atlas) でリリースされる機能と一部異なります。MongoDB Community Edition は自動暗号化をサポートしていません。
ドライバーのバージョンと暗号化データベースのバージョンについては、「Queryable Encryption Compatibility」をご参照ください。
制限
暗号化されたコレクションに対する診断コマンドの結果とクエリログは、編集されたり非表示になったりするため、問題分析に影響を及ぼす可能性があります。
aggregate, count, find, insert, update, deleteなどのコマンドは、暗号化されたコレクションに適用される場合、スロークエリログおよびプロファイラーに記録されません。collStats, currentOp, top, $planCacheStatsなどの診断コマンドの結果は編集され、結果の一部のフィールドが非表示になります。
暗号化フィールド間の競合により書き込み遅延が増加する場合があります。
contentionFactorのデフォルト値は 8 です。サイズが 1 GB を超えるメタデータコレクションは、手動でコンパクションを行う必要があります。詳細については、「Metadata Collection Compaction」をご参照ください。
encryptedFieldsMapオブジェクトは、オブジェクト内のクエリフィールドを含めて変更できません。クエリ可能暗号化機能は、レプリカセットまたはシャードクラスターインスタンスでのみサポートされます。
セカンダリノード上でクエリ可能暗号化を有効にしたデータは読み取れません。
updateManyまたはbulkWriteコマンドを実行してドキュメントを一括更新することはできず、findAndModifyコマンドのパラメーターは制限されます。アップサートのセマンティクスはサポートされません。アップサートがトリガーされた場合、暗号化フィールドは挿入されません。
クライアントサイドフィールドレベル暗号化 (CSFLE) 機能は、クエリ可能暗号化機能と同時にコレクションで有効にできません。また、CSFLE を有効にしたコレクションまたは暗号化されていないコレクションを、クエリ可能暗号化を有効にしたコレクションへ変換することもできません。
クエリ可能暗号化機能は、新規の空のコレクションに対してのみ有効にできます。
暗号化フィールドを含むコレクションの名前は変更できません。
$renameコマンドを実行してフィールド名を変更することもできません。暗号化されたコレクションの作成時に
jsonSchemaを指定する場合、encryptキーワードを含めることはできません。ビュー、時系列コレクション、キャップ付きコレクションはサポートされません。
TTL インデックスまたは一意インデックスはサポートされません。
jsonSchemaを無効にできません。コレクションは、クエリ可能暗号化が有効な MongoClient を使用して削除する必要があります。そうしない場合、メタデータが残ってしまいます。
クエリ可能暗号化機能は照合をサポートしていません。照合により、暗号化フィールドの通常のソートがブロックされます。
_idフィールドを暗号化フィールドとして指定できません。クエリ可能暗号化機能でサポートされるコマンドと演算子の数は限られています。詳細については、「Supported Operations for Queryable Encryption」をご参照ください。
事前準備
このガイドでは、クライアントとして ECS インスタンスを使用し、手順のデモンストレーションを行います。テスト環境に必要な依存関係がすでにある場合は、該当する手順をスキップできます。このガイドでは Node.js ドライバーを使用してデモンストレーションを行います。これは、mongosh が自動暗号化のみをサポートし、MongoDB Community Edition は明示的な暗号化のみをサポートするためです。
-
Node.js と npm をインストールします。
curl -fsSL https://rpm.nodesource.com/setup_lts.x | sudo bash - sudo yum install nodejs node -v npm -v MongoDB 用の公式 Node.js ドライバーをインストールします。
mkdir node_quickstart cd node_quickstart npm init -y npm install mongodb@6.6-
libmongocrypt ライブラリをインストールします。
vi /etc/yum.repos.d/libmongocrypt.repo // ファイルに次の内容を追加します。 [libmongocrypt] name=libmongocrypt repository baseurl=https://libmongocrypt.s3.amazonaws.com/yum/redhat/8/libmongocrypt/1.8/x86_64 gpgcheck=1 enabled=1 gpgkey=https://pgp.mongodb.com/libmongocrypt.asc // ライブラリをインストールします。 sudo yum install -y libmongocrypt Node.js ドライバーが依存する mongodb-client-encryption パッケージをインストールします。
sudo yum groupinstall 'Development Tools' npm install mongodb-client-encryption-
mongosh をインストールし、MONGODB_URI 環境変数を設定します。
wget https://repo.mongodb.org/yum/redhat/8/mongodb-org/7.0/x86_64/RPMS/mongodb-mongosh-2.2.5.x86_64.rpm yum install -y ./mongodb-mongosh-2.2.5.x86_64.rpm export MONGODB_URI="mongodb://root:xxxxxx@dds-2zef23cef14b4f142.mongodb.pre.rds.aliyuncs.com:3717,dds-2zef23cef14b4f141.mongodb.pre.rds.aliyuncs.com:3717/admin?replicaSet=mgset-855706" // 接続をテストします。 mongosh ${MONGODB_URI} 自動暗号化用の共有ライブラリを取得します。
Download Center で、お使いのマシンおよびディストリビューションのバージョンに対応するクライアントを選択し、crypt_shared パッケージを選択します。詳細については、「MongoDB Enterprise Server Download」をご参照ください。
// ローカルディレクトリに展開し、lib/mongo_crypt_v1.so ファイルを取得します。 tar -xzvf mongo_crypt_shared_v1-linux-x86_64-enterprise-rhel80-7.0.9.tgz
手順
MongoDB Community Edition は自動暗号化をサポートしていません。そのため、この記事では明示的な暗号化のプロセスを説明します。
Node.js の Read-Eval-Print Loop (REPL) 環境を起動し、次の手順を実行します。
node -i -e "const MongoClient = require('mongodb').MongoClient; const ClientEncryption = require('mongodb').ClientEncryption;"
-
カスタマーマスターキー (CMK) を作成します。
説明次の例は、ローカル KMS プロバイダーのサンプル構成を示しています。本番環境では、この構成を使用しないことを推奨します。
96 バイトの CMK を作成し、ローカルファイルシステム上の
customer-master-key.txtファイルに CMK を保存します。const fs = require("fs"); const crypto = require("crypto"); try { fs.writeFileSync("customer-master-key.txt", crypto.randomBytes(96)); } catch (err) { console.error(err); }この例では、Node.js の crypto 呼び出しを使用して CMK を生成します。代わりに、シェルで
/dev/urandomを使用して 96 バイトの CMK を生成することもできます。echo $(head -c 96 /dev/urandom | base64 | tr -d '\n') 変数を初期化します。
// KMS プロバイダー名は、"aws"、"gcp"、"azure"、"kmip"、"local" のいずれかである必要があります。 const kmsProviderName = "local"; const uri = process.env.MONGODB_URI; const keyVaultDatabaseName = "encryption"; const keyVaultCollectionName = "__keyVault"; const keyVaultNamespace = "encryption.__keyVault"; const encryptedDatabaseName = "medicalRecords"; const encryptedCollectionName = "patients";上記のサンプルコードでは、次の変数を初期化しています。
kmsProviderName:KMS プロバイダーの名前。この例では、localを使用します。uri:MongoDB URI。MongoDB URI は、MONGODB_URI環境変数で指定できます。MongoDB URI を直接指定することもできます。keyVaultDatabaseName:データ暗号化キー (DEK) を格納するデータベースの名前。keyVaultCollectionName:DEK を格納するコレクションの名前。このコレクションは通常のコレクションとは別である必要があります。keyVaultNamespace:keyVaultDatabaseNameとkeyVaultCollectionNameを<database>.<collection>の形式で連結した文字列。encryptedDatabaseName:暗号化データを格納するデータベースの名前。encryptedCollectionName:暗号化データを格納するコレクションの名前。
DEK を格納するコレクションに一意インデックスを作成します。
const keyVaultClient = new MongoClient(uri); await keyVaultClient.connect(); const keyVaultDB = keyVaultClient.db(keyVaultDatabaseName); // DEK を格納するデータベースと同名のデータベースを削除して、余分なデータが残らないようにします。 await keyVaultDB.dropDatabase(); const keyVaultColl = keyVaultDB.collection(keyVaultCollectionName); await keyVaultColl.createIndex( { keyAltNames: 1 }, { unique: true, partialFilterExpression: { keyAltNames: { $exists: true } }, } ); // 再確認 await keyVaultColl.indexes();暗号化されたコレクションを作成します。
作成した CMK を取得し、KMS プロバイダーを指定します。
const localMasterKey = fs.readFileSync("./customer-master-key.txt"); kmsProviders = {local: {key: localMasterKey}};DEK を作成します。
説明この手順を実行する前に、
uri変数で指定したユーザーがencryption._keyVaultとmedicalRecordsデータベースに対するdbAdmin権限を持っていることを確認してください。const clientEnc = new ClientEncryption(keyVaultClient, { keyVaultNamespace: keyVaultNamespace, kmsProviders: kmsProviders, }); const dek1 = await clientEnc.createDataKey(kmsProviderName, { keyAltNames: ["dataKey1"], }); const dek2 = await clientEnc.createDataKey(kmsProviderName, { keyAltNames: ["dataKey2"], });暗号化するフィールドを指定し、作成した DEK を設定します。
const encryptedFieldsMap = { [`${encryptedDatabaseName}.${encryptedCollectionName}`]: { fields: [ { keyId: dek1, path: "patientId", bsonType: "int", queries: { queryType: "equality" }, }, { keyId: dek2, path: "medications", bsonType: "array", }, ], }, };自動暗号化用の共有ライブラリを指定し、MongoClient を作成します。
const extraOptions = {cryptSharedLibPath: "/root/lib/mongo_crypt_v1.so"}; const encClient = new MongoClient(uri, { autoEncryption: { keyVaultNamespace, kmsProviders, extraOptions, encryptedFieldsMap, }, }); await encClient.connect();暗号化されたコレクションを作成します。
const newEncDB = encClient.db(encryptedDatabaseName); await newEncDB.dropDatabase(); await newEncDB.createCollection(encryptedCollectionName);
読み取りおよび書き込み操作の暗号化に使用する MongoClient を作成します。
作成した DEK を格納するコレクションを指定します。
const eDB = "encryption"; const eKV = "__keyVault"; const keyVaultNamespace = `${eDB}.${eKV}`; const secretDB = "medicalRecords"; const secretCollection = "patients";作成した CMK を指定します。
重要本番環境ではローカルキーファイルを使用しないでください。
const fs = require("fs"); const path = "./customer-master-key.txt"; const localMasterKey = fs.readFileSync(path); const kmsProviders = { local: { key: localMasterKey, }, };作成した DEK を取得します。
説明DEK 名は、手順 4 のステップ 2 で作成した DEK 名と同じである必要があります。
const uri = process.env.MONGODB_URI;; const unencryptedClient = new MongoClient(uri); await unencryptedClient.connect(); const keyVaultClient = unencryptedClient.db(eDB).collection(eKV); const dek1 = await keyVaultClient.findOne({ keyAltNames: "dataKey1" }); const dek2 = await keyVaultClient.findOne({ keyAltNames: "dataKey2" });自動暗号化用の共有ライブラリを指定し、MongoClient を作成します。
const extraOptions = { cryptSharedLibPath: "/root/lib/mongo_crypt_v1.so", }; const encryptedClient = new MongoClient(uri, { autoEncryption: { kmsProviders: kmsProviders, keyVaultNamespace: keyVaultNamespace, bypassQueryAnalysis: true, keyVaultClient: unencryptedClient, extraOptions: extraOptions, }, }); await encryptedClient.connect();ClientEncryption オブジェクトを作成します。
const encryption = new ClientEncryption(unencryptedClient, { keyVaultNamespace, kmsProviders, });
暗号化フィールドを含むドキュメントを、作成した暗号化されたコレクションに挿入します。
const patientId = 12345678; const medications = ["Atorvastatin", "Levothyroxine"]; const indexedInsertPayload = await encryption.encrypt(patientId, { algorithm: "Indexed", keyId: dek1._id, contentionFactor: 1, }); const unindexedInsertPayload = await encryption.encrypt(medications, { algorithm: "Unindexed", keyId: dek2._id, }); const encryptedColl = encryptedClient.db(secretDB).collection(secretCollection); await encryptedColl.insertOne({ firstName: "Jon", patientId: indexedInsertPayload, medications: unindexedInsertPayload, });-
作成した暗号化されたコレクションに対してフィールドレベルのクエリを実行します。
const findPayload = await encryption.encrypt(patientId, { algorithm: "Indexed", keyId: dek1._id, queryType: "equality", contentionFactor: 1, }); console.log(await encryptedColl.findOne({ patientId: findPayload }));次の例は、返されたドキュメントを示しています。
> console.log(await encryptedColl.findOne({ patientId: findPayload })); { _id: new ObjectId('6645b56f58abf955ebd95caf'), firstName: 'Jon', patientId: 12345678, medications: [ 'Atorvastatin', 'Levothyroxine' ], __safeContent__: [ Binary.createFromBase64('IrPf972hlhDvnasQH6rIAW6BqERo0ZEgC6C0/zNQiIY=', 0) ] } -
暗号化フィールドにアクセスするには、暗号化オプションを含むクライアントを使用する必要があります。そうしない場合、暗号化フィールドにはアクセスできません。
フィールドレベルのクエリには、暗号化されていない
unencryptedClientクライアントを使用します。console.log(await unencryptedClient.db(secretDB).collection(secretCollection).findOne());次の例は、返されたドキュメントを示しています。機密フィールドはバイナリデータとして表示されます。
> console.log(await unencryptedClient.db(secretDB).collection(secretCollection).findOne()); { _id: new ObjectId('6645b56f58abf955ebd95caf'), firstName: 'Jon', patientId: Binary.createFromBase64('DtmNrEDyTEBDidZxWkbGU/MQdYNxwmnqYj5tSr9uhHbwWj8bsSD3TWlZ8aMMvw6FY00cmdc1QLLoEX3NwlKRhz0zax9LcQhN3vKUf4eq3hAfBYWkyOQxsiwbPsU0AiXnMV+qM6J2p2JZGLrDvxfbTY+obBmRqdvlgJ51dKmYopvDNToWBXDQkqAis9/3vaGWE0+dqxAfqsgBboGoRGjRkSALoI7/M1CIhvmpds5LR7/232uI4f5QDbk0JVfjnI0Doov6b0GrAXe9', 6), medications: Binary.createFromBase64('EKqAnwBkWUBon0Qf9sZHVIkEphUdfDK/aqYPs5M1Xc58CkojwX0kvC+KjwYyEozia41F5cnD9NFBwnVuDJUaqjTLc1YwG1DEIUZdcYCMf3JiureqA0voYP3gZxPyFmf/h1DS80Jz+g', 6), __safeContent__: [ Binary.createFromBase64('IrPf972hlhDvnasQH6rIAW6BqERo0ZEgC6CO/zNQiIY=', 0) ] }mongosh を使用して外部から暗号化フィールドにアクセスすることもできます。これは、クライアントキーなしで作成した暗号化されたコレクションにアクセスする状況をシミュレートします。
// 別のターミナルセッションで、mongosh を使用して MongoDB URI に直接接続します。 mongosh ${MONGODB_URI} db.getSiblingDB("medicalRecords").patients.findOne()次の例は、返されたドキュメントを示しています。
mgset-855706 [primary] admin> db.getSiblingDB("medicalRecords").patients.findOne() { _id: ObjectId('6645b56f58abf955ebd95caf'), firstName: 'Jon', patientId: Binary.createFromBase64('DtmNrEDyTEBYDidZxWkbGU/MQdYNxwmnqYj5tSr9uhHbwWj8bsSD3TWlZ8aMMvw6fYO0cmdc1QLoEX3Nw1KRhz0zax9LcQhN3vKUf4eq3hAfBYWkyQxsiwbPsU0AiXnMV+qM6JZGLrDvxfbTY+obBmRqdvlJ51dKmYopvDNTowBXdQkqAis9/3vaGWEO+dqxAfqsgBboGoRGjRkSALoI7/M1CIhvmpds5LR7/232uI4f5QDbk0JVfjnIODoov6b0GrAXe9', 6), medications: Binary.createFromBase64('EKqAnwBkWUBon0Qf9sZHVIkEphUdfdk/aqYPs5M1Xc58CkojX0kvC+KjwYyEozia41F5cnD9NFBwnVuDJUaqjTLc1YwG1DEIUZdcYMf3liuregA0qvoYP3qZxPyFmf/h1IJz+g', 6), __safeContent__: [ Binary.createFromBase64('IrPf972hlhDvnasQH6rIAW6BqERo0ZEgC6CO/zNQiIY=', 0) ] }