123456789_123456789_123456789_123456789_123456789_

Automatic Client-Side Field Level Encryption

Since version 4.2 MongoDB supports Client-Side Field Level Encryption (CSFLE). This is a feature that enables you to encrypt data in your application before you send it over the network to MongoDB. With CSFLE enabled, no MongoDB product has access to your data in an unencrypted form.

You can set up CSFLE using the following mechanisms:

Starting with version 9.0, Mongoid supports CSFLE's Automatic Encryption feature. This tutorial walks you through the process of setting up and using CSFLE in Mongoid.

Note

This tutorial does not cover all CSLFE features. You can find more information about MongoDB CSFLE in the server documentation.

Note

If you want to use explicit FLE, please follow the Ruby driver documentation.

Installation

You can find the detailed description of how to install the necessary dependencies in the driver documentation.

Note the version of the Ruby driver being used in your application and select the appropriate steps below.

Install libmongocrypt

This can be done one of two ways.

Install the automatic encryption shared library (Ruby driver 2.19+)

If you use the Ruby driver version 2.19 and above, the automatic encryption shared library should be installed by following the instructions on the Automatic Encryption Shared Library for Queryable Encryption </core/queryable-encryption/reference/shared-library/#download-the-automatic-encryption-shared-library> page in the Server manual.

The steps required are as follows:

  1. Navigate to the MongoDB Download Center
  2. From the Version dropdown, select x.y.z (current) (the latest current version).
  3. In the Platform dropdown, select your platform.
  4. In the Package dropdown, select crypt_shared.
  5. Click Download.

Once extracted, ensure the full path to the library is configured within your mongoid.yml as follows:

development:
  clients:
    default:
      options:
        auto_encryption_options:
          extra_options:
            crypt_shared_lib_path: '/path/to/mongo_crypt_v1.so'

Install the mongocryptd (Ruby driver 2.18 or older)

If you are using an older version of the Ruby driver mongocryptd will need to be installed manually. mongocryptd comes pre-packaged with enterprise builds of the MongoDB server (versions 4.2 and newer). For installation instructions, see the MongoDB manual.

Add ffi to your Gemfile

The MongoDB Ruby driver uses the ffi gem to call functions from libmongocrypt. As this gem is not a dependency of the driver, it will need to be manually added to your Gemfile:

gem 'ffi'

Create a Customer Master Key

A Customer Master Key (CMK) is used to encrypt Data Encryption Keys. The easiest way is to use a locally stored 96-bytes key. You can generate such a key using the following Ruby code:

require 'securerandom'

SecureRandom.hex(48) # => "f54ab...."

Later in this tutorial we assume that the Customer Master Key is available from the CUSTOMER_MASTER_KEY environment variable.

Warning

Using a local master key is insecure. It is recommended that you use a remote Key Management Service to create and store your master key. To do so, follow steps of the "Set up a Remote Master Key" in the MongoDB Client-Side Encryption documentation.

For more information about creating a master key, see the Create a Customer Master Key section of the MongoDB manual.

Configure Clients

Automatic CSFLE requires some additional configuration for the MongoDB client. Assuming that your application has just one default client, you need to add the following to your mongoid.yml:

development:
  clients:
    default:
      uri: mongodb+srv://user:pass@yourcluster.mongodb.net/blog_development?retryWrites=true&w=majority
      options:
        auto_encryption_options: # This key enables automatic encryption
          key_vault_namespace: 'encryption.__keyVault' # Database and collection to store data keys
          kms_providers: # Tells the driver where to obtain master keys
            local: # We use the local key in this tutorial
              key: "<%= ENV['CUSTOMER_MASTER_KEY'] %>" # Key that we generated earlier
          extra_options:
            crypt_shared_lib_path: '/path/to/mongo_crypt_v1.so' # Only if you use the library

Create a Data Encryption Key

A Data Encryption Key (DEK) is the key you use to encrypt the fields in your MongoDB documents. You store your Data Encryption Key in your Key Vault collection encrypted with your CMK.

To create a DEK in Mongoid you can use the db:mongoid:encryption:create_data_key Rake task:

% rake db:mongoid:encryption:create_data_key
Created data key with id: 'KImyywsTQWi1+cFYIHdtlA==' for kms provider: 'local' in key vault: 'encryption.__keyVault'.

You can create multiple DEKs, if necessary.

% rake db:mongoid:encryption:create_data_key
Created data key with id: 'Vxr5m+5cQISjDOruzZgE0w==' for kms provider: 'local' in key vault: 'encryption.__keyVault'.

You can also provide an alternate name for the DEK. This allows you to reference the DEK by name when configuring encryption for your fields. It also allows you to dynamically assign a DEK to a field at runtime.

% rake db:mongoid:encryption:create_data_key -- --key-alt-name=my_data_key
Created data key with id: 'yjF8hKmKQsqGeFGXlB9Sow==' with key alt name: 'my_data_key' for kms provider: 'local' in key vault: 'encryption.__keyVault'.

Configure Encryption Schema

Now we can tell Mongoid what should be encrypted:

class Patient
  include Mongoid::Document
  include Mongoid::Timestamps

  # Tells Mongoid what DEK should be used to encrypt fields of the document
  # and its embedded documents.
  encrypt_with key_id: 'KImyywsTQWi1+cFYIHdtlA=='

  # This field is not encrypted.
  field :category, type: String

  # This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Random
  # algorithm.
  field :passport_id, type: String, encrypt: {
      deterministic: false
  }
  # This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic
  # algorithm.
  field :blood_type, type: String, encrypt: {
      deterministic: true
  }
  # This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Random
  # algorithm and using  a different data key.
  field :ssn, type: Integer, encrypt: {
      deterministic: false, key_id: 'Vxr5m+5cQISjDOruzZgE0w=='
  }

  embeds_one :insurance
end

class Insurance
  include Mongoid::Document
  include Mongoid::Timestamps

  field :insurer, type: String

  # This field is encrypted using AEAD_AES_256_CBC_HMAC_SHA_512-Random
  # algorithm using the key which alternate name is stored in the
  # policy_number_key field.
  field :policy_number, type: Integer, encrypt: {
      deterministic: false,
      key_name_field: :policy_number_key
  }

  embedded_in :patient
end
Note

If you are developing a Rails application, it is recommended to set preload_models to true in mongoid.yml. This will ensure that Mongoid loads all models before the application starts, and the encryption schema is configured before any data is read or written.

Known Limitations

Working with Data

Automatic CSFLE usage is transparent in many situations.

Note

In code examples below we assume that there is a variable unencrypted_client that is a MongoClient connected to the same database but without encryption. We use this client to demonstrate what is actually persisted in the database.

Documents can be created as usual, fields will be encrypted and decrypted according to the configuration:

Patient.create!(
  category: 'ER',
  passport_id: '123456',
  blood_type: 'AB+',
  ssn: 98765,
  insurance: Insurance.new(insurer: 'TK', policy_number: 123456, policy_number_key: 'my_data_key')
)

# Fields are encrypted in the database
unencrypted_client['patients'].find.first
# =>
# {"_id"=>BSON::ObjectId('6446a1d046ebfd701f9f4292'),
# "category"=>"ER",
# "passport_id"=><BSON::Binary:0x404080 type=ciphertext data=0x012889b2cb0b1341...>,
# "blood_type"=><BSON::Binary:0x404560 type=ciphertext data=0x022889b2cb0b1341...>,
# "ssn"=><BSON::Binary:0x405040 type=ciphertext data=0x012889b2cb0b1341...>,
# "insurance"=>{"_id"=>BSON::ObjectId('6446a1d046ebfd701f9f4293'), "insurer"=>"TK", "policy_number"=><BSON::Binary:0x405920 type=ciphertext data=0x012889b2cb0b1341...>}, "policy_number_key"=>"my_data_key"}

Fields encrypted using a deterministic algorithm can be queried. Only exact match queries are supported. For more details please consult the server documentation.

# We can find documents by deterministically encrypted fields.
Patient.where(blood_type: "AB+").to_a
# => [#<Patient _id: 6447e34d46ebfd3debdd9c39, category: "ER", passport_id: "123456", blood_type: "AB+", ssn: 98765>]

Encryption Key Management

Customer Master Keys

Your Customer Master Key is the key you use to encrypt your Data Encryption Keys. MongoDB automatically encrypts Data Encryption Keys using the specified CMK during Data Encryption Key creation.

The CMK is the most sensitive key in CSFLE. If your CMK is compromised, all of your encrypted data can be decrypted.

Important

Ensure you store your Customer Master Key (CMK) on a remote KMS.

To learn more about why you should use a remote KMS, see Reasons to Use a Remote KMS.

To view a list of all supported KMS providers, see the KMS Providers page.

MongoDB CSFLE supports the following Key Management System (KMS) providers:

: - Amazon Web Services KMS - Azure Key Vault - Google Cloud Platform KMS - Any KMIP Compliant Key Management System - Local Key Provider (for testing only)

Data Encryption Keys

Data Encryption Keys can be created using the db:mongoid:encryption:create_data_key Rake task. By default they are stored on the same cluster as the database. However, it might be a good idea to store the keys separately. This can be done by specifying a key vault client in mongoid.yml:

development:
  clients:
    key_vault:
      uri: mongodb+srv://user:pass@anothercluster.mongodb.net/blog_development?retryWrites=true&w=majority
    default:
      uri: mongodb+srv://user:pass@yourcluster.mongodb.net/blog_development?retryWrites=true&w=majority
      options:
        auto_encryption_options:
          key_vault_client: :key_vault # Client to connect to key vault
          # ...

Encryption Keys Rotation

You can rotate encryption keys using the rewrap_many_data_key method of the Ruby driver. This method automatically decrypts multiple data encryption keys and re-encrypts them using a specified CMK. It then updates the rotated keys in the key vault collection. This method allows you to rotate encryption keys based on two optional arguments:

Here is an example of rotating keys using AWS KMS:

# Create a key vault client
key_vault_client = Mongo::Client.new('mongodb+srv://user:pass@yourcluster.mongodb.net')
# Or, if you declared the key value client in mongoid.yml, use it
key_vault_client = Mongoid.client(:key_vault)

# Create the encryption object
encryption = Mongo::ClientEncryption.new(
  key_vault_client,
  key_vault_namespace: 'encryption.__keyVault',
  kms_providers: {
    aws: {
      "accessKeyId": "<IAM User Access Key ID>",
      "secretAccessKey": "<IAM User Secret Access Key>"
    }
  }
)

encryption.rewrap_many_data_key(
  {}, # We want to rewrap all keys
  {
    provider: 'aws',
    master_key: {
      region: 'us-east-2',
      key: 'arn:aws:kms:us-east-2:...'
    }
  }
)

Adding Automatic Encryption To Existing Project

MongoDB automatic CSFLE supports encryption in place. You can enable encryption for your existing database, and will still able to read unencrypted data. All data written to the database will be encrypted. However, as soon as the encryption is enabled, all query operations will use encrypted data:

# We assume that there are two documents in the database, one created without
# encryption enabled, and one with encryption.

# We can still read both.
Patient.all.to_a
# =>
# [#<Patient _id: 644937ac46ebfd02468e58c8, category: "ER", passport_id: "DE-1257", blood_type: "AB+", ssn: 123456>,
# #<Patient _id: 644937c946ebfd029309b912, category: "ER", passport_id: "AT-1545", blood_type: "AB+", ssn: 987654>]

# But when we query, we can see only the latter one.
Patient.where(blood_type: 'AB+').to_a
# => [#<Patient _id: 644937c946ebfd029309b912, category: "ER", passport_id: "AT-1545", blood_type: "AB+", ssn: 987654>]

If you want to encrypt the existing database, it can be achieved by reading and writing back all data, even without any changes. If you decide to do so, please keep the following in mind: