class Mongo::Crypt::AutoEncrypter

An AutoEcnrypter is an object that encapsulates the behavior of automatic encryption. It controls all resources associated with auto-encryption, including the libmongocrypt handle, key vault client object, mongocryptd client object, and encryption I/O.

The AutoEncrypter is kept as an instance on a Mongo::Client. Client objects with the same auto_encryption_options Hash may share AutoEncrypters.

@api private

Constants

DEFAULT_EXTRA_OPTIONS

A Hash of default values for the :extra_options option

Attributes

key_vault_client[R]
metadata_client[R]
mongocryptd_client[R]
options[R]

Public Class Methods

new(options) click to toggle source

Set up encryption-related options and instance variables on the class that includes this module. Calls the same method on the Mongo::Crypt::Encrypter module.

@param [ Hash ] options

@option options [ Mongo::Client ] :client A client connected to the

encrypted collection.

@option options [ Mongo::Client | nil ] :key_vault_client A client connected

to the MongoDB instance containing the encryption key vault; optional.
If not provided, will default to :client option.

@option options [ String ] :key_vault_namespace The namespace of the key

vault in the format database.collection.

@option options [ Hash | nil ] :schema_map The JSONSchema of the collection(s)

with encrypted fields. This option is mutually exclusive with :schema_map_path.

@option options [ String | nil ] :schema_map_path A path to a file contains the JSON schema

of the collection that stores auto encrypted documents. This option is
mutually exclusive with :schema_map.

@option options [ Boolean | nil ] :bypass_auto_encryption When true, disables

auto-encryption. Default is false.

@option options [ Hash | nil ] :extra_options Options related to spawning

mongocryptd. These are set to default values if no option is passed in.

@option options [ Hash ] :kms_providers A hash of key management service

configuration information.
@see Mongo::Crypt::KMS::Credentials for list of options for every
supported provider.
@note There may be more than one KMS provider specified.

@option options [ Hash ] :kms_tls_options TLS options to connect to KMS

providers. Keys of the hash should be KSM provider names; values
should be hashes of TLS connection options. The options are equivalent
to TLS connection options of Mongo::Client.
@see Mongo::Client#initialize for list of TLS options.

@option options [ Hash | nil ] :encrypted_fields_map maps a collection

namespace to an encryptedFields.
- Note: If a collection is present on both the encryptedFieldsMap
  and schemaMap, an error will be raised.

@option options [ Boolean | nil ] :bypass_query_analysis When true

disables automatic analysis of outgoing commands.

@option options [ String | nil ] :crypt_shared_lib_path Path that should

be  the used to load the crypt shared library. Providing this option
overrides default crypt shared library load paths for libmongocrypt.

@option options [ Boolean | nil ] :crypt_shared_lib_required Whether

crypt shared library is required. If 'true', an error will be raised
if a crypt_shared library cannot be loaded by libmongocrypt.

@raise [ ArgumentError ] If required options are missing or incorrectly

formatted.
# File lib/mongo/crypt/auto_encrypter.rb, line 93
def initialize(options)
  Crypt.validate_ffi!
  # Note that this call may eventually, via other method invocations,
  # create additional clients which have to be cleaned up.
  @options = set_default_options(options).freeze

  @crypt_handle = Crypt::Handle.new(
    Crypt::KMS::Credentials.new(@options[:kms_providers]),
    Crypt::KMS::Validations.validate_tls_options(@options[:kms_tls_options]),
    schema_map: @options[:schema_map],
    schema_map_path: @options[:schema_map_path],
    encrypted_fields_map: @options[:encrypted_fields_map],
    bypass_query_analysis: @options[:bypass_query_analysis],
    crypt_shared_lib_path: @options[:extra_options][:crypt_shared_lib_path],
    crypt_shared_lib_required: @options[:extra_options][:crypt_shared_lib_required],
  )

  @mongocryptd_options = @options[:extra_options].slice(
    :mongocryptd_uri,
    :mongocryptd_bypass_spawn,
    :mongocryptd_spawn_path,
    :mongocryptd_spawn_args
  )
  @mongocryptd_options[:mongocryptd_bypass_spawn] = @options[:bypass_auto_encryption] ||
    @options[:extra_options][:mongocryptd_bypass_spawn] ||
    @crypt_handle.crypt_shared_lib_available? ||
    @options[:extra_options][:crypt_shared_lib_required]

  unless @options[:extra_options][:crypt_shared_lib_required] || @crypt_handle.crypt_shared_lib_available? || @options[:bypass_query_analysis]
    # Set server selection timeout to 1 to prevent the client waiting for a
    # long timeout before spawning mongocryptd
    @mongocryptd_client = Client.new(
      @options[:extra_options][:mongocryptd_uri],
      monitoring_io: @options[:client].options[:monitoring_io],
      populator_io: @options[:client].options[:populator_io],
      server_selection_timeout: 10,
      database: @options[:client].options[:database]
    )
  end

  begin
    @encryption_io = EncryptionIO.new(
      client: @options[:client],
      mongocryptd_client: @mongocryptd_client,
      key_vault_namespace: @options[:key_vault_namespace],
      key_vault_client: @key_vault_client,
      metadata_client: @metadata_client,
      mongocryptd_options: @mongocryptd_options
    )
  rescue
    begin
      @mongocryptd_client&.close
    rescue => e
      log_warn("Error closing mongocryptd client in auto encrypter's constructor: #{e.class}: #{e}")
      # Drop this exception so that the original exception is raised
    end
    raise
  end
rescue
  if @key_vault_client && @key_vault_client != options[:client] &&
    @key_vault_client.cluster != options[:client].cluster
  then
    begin
      @key_vault_client.close
    rescue => e
      log_warn("Error closing key vault client in auto encrypter's constructor: #{e.class}: #{e}")
      # Drop this exception so that the original exception is raised
    end
  end

  if @metadata_client && @metadata_client != options[:client] &&
    @metadata_client.cluster != options[:client].cluster
  then
    begin
      @metadata_client.close
    rescue => e
      log_warn("Error closing metadata client in auto encrypter's constructor: #{e.class}: #{e}")
      # Drop this exception so that the original exception is raised
    end
  end

  raise
end

Public Instance Methods

close() click to toggle source

Close the resources created by the AutoEncrypter.

@return [ true ] Always true.

# File lib/mongo/crypt/auto_encrypter.rb, line 217
def close
  @mongocryptd_client.close if @mongocryptd_client

  if @key_vault_client && @key_vault_client != options[:client] &&
    @key_vault_client.cluster != options[:client].cluster
  then
    @key_vault_client.close
  end

  if @metadata_client && @metadata_client != options[:client] &&
    @metadata_client.cluster != options[:client].cluster
  then
    @metadata_client.close
  end

  true
end
decrypt(command) click to toggle source

Decrypt a database command.

@param [ Hash ] command The command with encrypted fields.

@return [ BSON::Document ] The decrypted command.

# File lib/mongo/crypt/auto_encrypter.rb, line 206
def decrypt(command)
  AutoDecryptionContext.new(
    @crypt_handle,
    @encryption_io,
    command
  ).run_state_machine
end
encrypt(database_name, command) click to toggle source

Encrypt a database command.

@param [ String ] database_name The name of the database on which the

command is being run.

@param [ Hash ] command The command to be encrypted.

@return [ BSON::Document ] The encrypted command.

# File lib/mongo/crypt/auto_encrypter.rb, line 192
def encrypt(database_name, command)
  AutoEncryptionContext.new(
    @crypt_handle,
    @encryption_io,
    database_name,
    command
  ).run_state_machine
end
encrypt?() click to toggle source

Whether this encrypter should perform encryption (returns false if the :bypass_auto_encryption option is set to true).

@return [ Boolean ] Whether to perform encryption.

# File lib/mongo/crypt/auto_encrypter.rb, line 181
def encrypt?
  !@options[:bypass_auto_encryption]
end

Private Instance Methods

internal_client(client) click to toggle source

Creates or return already created internal client to be used for auto encryption.

@param [ Mongo::Client ] client A client connected to the

encrypted collection.

@return [ Mongo::Client ] Client to be used as internal client for auto encryption.

# File lib/mongo/crypt/auto_encrypter.rb, line 297
def internal_client(client)
  @internal_client ||= client.with(
    auto_encryption_options: nil,
    min_pool_size: 0,
    monitoring: client.send(:monitoring),
  )
end
set_default_options(options) click to toggle source

Returns a new set of options with the following changes:

  • sets default values for all extra_options

  • adds –idleShtudownTimeoutSecs=60 to extra_options if not already present

  • sets bypass_auto_encryption to false

  • sets default key vault client

# File lib/mongo/crypt/auto_encrypter.rb, line 243
def set_default_options(options)
  opts = options.dup

  extra_options = opts.delete(:extra_options) || Options::Redacted.new
  extra_options = DEFAULT_EXTRA_OPTIONS.merge(extra_options)

  has_timeout_string_arg = extra_options[:mongocryptd_spawn_args].any? do |elem|
    elem.is_a?(String) && elem.match(/\A--idleShutdownTimeoutSecs=\d+\z/)
  end

  timeout_int_arg_idx = extra_options[:mongocryptd_spawn_args].index('--idleShutdownTimeoutSecs')
  has_timeout_int_arg = timeout_int_arg_idx && extra_options[:mongocryptd_spawn_args][timeout_int_arg_idx + 1].is_a?(Integer)

  unless has_timeout_string_arg || has_timeout_int_arg
    extra_options[:mongocryptd_spawn_args] << '--idleShutdownTimeoutSecs=60'
  end

  opts[:bypass_auto_encryption] ||= false
  set_or_create_clients(opts)
  opts[:key_vault_client] = @key_vault_client

  Options::Redacted.new(opts).merge(extra_options: extra_options)
end
set_or_create_clients(options) click to toggle source

Create additional clients for auto encryption, if necessary

@param [ Hash ] options Auto encryption options.

# File lib/mongo/crypt/auto_encrypter.rb, line 270
def set_or_create_clients(options)
  client = options[:client]
  @key_vault_client = if options[:key_vault_client]
    options[:key_vault_client]
  elsif client.options[:max_pool_size] == 0
    client
  else
    internal_client(client)
  end

  @metadata_client = if options[:bypass_auto_encryption]
    nil
  elsif client.options[:max_pool_size] == 0
    client
  else
    internal_client(client)
  end
end