123456789_123456789_123456789_123456789_123456789_

Class: Mongo::Crypt::EncryptionIO Private

Do not use. This class is for internal use only.
Relationships & Source Files
Inherits: Object
Defined in: lib/mongo/crypt/encryption_io.rb

Overview

A class that implements I/O methods between the driver and the MongoDB server or mongocryptd.

Constant Summary

Class Method Summary

Instance Method Summary

Instance Method Details

#add_key_alt_name(id, key_alt_name, timeout_ms: nil)

Adds a key_alt_name to the key_alt_names array of the key document in the key vault collection with the given id.

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 187

def add_key_alt_name(id, key_alt_name, timeout_ms: nil)
  key_vault_collection.find_one_and_update(
    { _id: id },
    { '$addToSet' => { keyAltNames: key_alt_name } },
    timeout_ms: timeout_ms
  )
end

#collection_info(db_name, filter, timeout_ms: nil) ⇒ Array<BSON::Document>

Get collection info for a collection matching the provided filter

Parameters:

  • filter (Hash)
  • :timeout_ms (Integer)

    The operation timeout in milliseconds. Must be a non-negative integer. An explicit value of 0 means infinite. The default value is unset which means the feature is not enabled.

Returns:

  • (Array<BSON::Document>)

    The collection information documents

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 101

def collection_info(db_name, filter, timeout_ms: nil)
  unless @metadata_client
    raise ArgumentError,
          'collection_info requires metadata_client to have been passed to the constructor, but it was not'
  end

  @metadata_client
    .use(db_name)
    .database
    .list_collections(filter: filter, deserialize_as_bson: true, timeout_ms: timeout_ms)
    .to_a
end

#delete_key(id, timeout_ms: nil)

Removes the key document with the given id from the key vault collection.

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 197

def delete_key(id, timeout_ms: nil)
  key_vault_collection.delete_one(_id: id, timeout_ms: timeout_ms)
end

#feed_kms(kms_context, tls_options, timeout_ms: nil)

Get information about the remote KMS encryption key and feed it to the the KmsContext object

Parameters:

  • kms_context (Mongo::Crypt::KmsContext)

    A KmsContext object corresponding to one remote KMS data key. Contains information about the endpoint at which to establish a TLS connection and the message to send on that connection.

  • tls_options. (Hash)

    TLS options to connect to KMS provider. The options are same as for ::Mongo::Client.

  • :timeout_ms (Integer)

    The operation timeout in milliseconds. Must be a non-negative integer. An explicit value of 0 means infinite. The default value is unset which means the feature is not enabled.

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 165

def feed_kms(kms_context, tls_options, timeout_ms: nil)
  with_ssl_socket(kms_context.endpoint, tls_options) do |ssl_socket|
    Timeout.timeout(timeout_ms || SOCKET_TIMEOUT, Error::SocketTimeoutError,
                    'Socket write operation timed out') do
      ssl_socket.syswrite(kms_context.message)
    end

    bytes_needed = kms_context.bytes_needed
    while bytes_needed > 0
      bytes = Timeout.timeout(timeout_ms || SOCKET_TIMEOUT, Error::SocketTimeoutError,
                              'Socket read operation timed out') do
        ssl_socket.sysread(bytes_needed)
      end

      kms_context.feed(bytes)
      bytes_needed = kms_context.bytes_needed
    end
  end
end

#find_keys(filter, timeout_ms: nil) ⇒ Array<BSON::Document>

Query for keys in the key vault collection using the provided filter

Parameters:

  • filter (Hash)
  • :timeout_ms (Integer)

    The operation timeout in milliseconds. Must be a non-negative integer. An explicit value of 0 means infinite. The default value is unset which means the feature is not enabled.

Returns:

  • (Array<BSON::Document>)

    The query results

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 77

def find_keys(filter, timeout_ms: nil)
  key_vault_collection.find(filter, timeout_ms: timeout_ms).to_a
end

#get_key(id, timeout_ms: nil)

Finds a single key document with the given id.

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 202

def get_key(id, timeout_ms: nil)
  key_vault_collection.find(_id: id, timeout_ms: timeout_ms).first
end

#get_key_by_alt_name(key_alt_name, timeout_ms: nil)

Returns a key document in the key vault collection with the given key_alt_name.

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 208

def get_key_by_alt_name(key_alt_name, timeout_ms: nil)
  key_vault_collection.find(keyAltNames: key_alt_name, timeout_ms: timeout_ms).first
end

#get_keys(timeout_ms: nil)

Finds all documents in the key vault collection.

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 213

def get_keys(timeout_ms: nil)
  key_vault_collection.find(nil, timeout_ms: timeout_ms)
end

#insert_data_key(document, timeout_ms: nil) ⇒ Mongo::Operation::Insert::Result

Insert a document into the key vault collection

Parameters:

  • document (Hash)
  • :timeout_ms (Integer)

    The operation timeout in milliseconds. Must be a non-negative integer. An explicit value of 0 means infinite. The default value is unset which means the feature is not enabled.

Returns:

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 89

def insert_data_key(document, timeout_ms: nil)
  key_vault_collection.insert_one(document, timeout_ms: timeout_ms)
end

#key_vault_collection (private)

Use the provided key vault client and namespace to construct a ::Mongo::Collection object representing the key vault collection.

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 278

def key_vault_collection
  @key_vault_collection ||= @key_vault_client.with(
    database: @key_vault_db_name,
    read_concern: { level: :majority },
    write_concern: { w: :majority }
  )[@key_vault_collection_name]
end

#mark_command(cmd, db_name: nil, timeout_ms: nil) ⇒ Hash

Send the command to mongocryptd to be marked with intent-to-encrypt markings

Parameters:

  • cmd (Hash)
  • :db_name (String | nil)

    The database against which the command is being run. When provided, the command is sent to mongocryptd using this database so that the namespace in the command matches the namespace in encryptionInformation.

  • :timeout_ms (Integer)

    The operation timeout in milliseconds. Must be a non-negative integer. An explicit value of 0 means infinite. The default value is unset which means the feature is not enabled.

Returns:

  • (Hash)

    The marked command

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 126

def mark_command(cmd, db_name: nil, timeout_ms: nil)
  unless @mongocryptd_client
    raise ArgumentError,
          'mark_command requires mongocryptd_client to have been passed to the constructor, but it was not'
  end

  # Ensure the response from mongocryptd is deserialized with { mode: :bson }
  # to prevent losing type information in commands
  options = {
    execution_options: { deserialize_as_bson: true },
    timeout_ms: timeout_ms
  }

  mongocryptd = db_name ? @mongocryptd_client.use(db_name) : @mongocryptd_client

  begin
    response = mongocryptd.database.command(cmd, options)
  rescue Error::NoServerAvailable => e
    raise e if @options[:mongocryptd_bypass_spawn]

    spawn_mongocryptd
    response = mongocryptd.database.command(cmd, options)
  end

  response.first
end

#remove_key_alt_name(id, key_alt_name, timeout_ms: nil)

Removes a key_alt_name from the key_alt_names array of the key document in the key vault collection with the given id.

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 219

def remove_key_alt_name(id, key_alt_name, timeout_ms: nil)
  key_vault_collection.find_one_and_update(
    { _id: id },
    [
      {
        '$set' => {
          keyAltNames: {
            '$cond' => [
              { '$eq' => [ '$keyAltNames', [ key_alt_name ] ] },
              '$$REMOVE',
              {
                '$filter' => {
                  input: '$keyAltNames',
                  cond: { '$ne' => [ '$$this', key_alt_name ] }
                }
              }
            ]
          }
        }
      }
    ],
    timeout_ms: timeout_ms
  )
end

#spawn_mongocryptdInteger (private)

Note:

To capture the mongocryptd logs, add "--logpath=/path/to/logs" to auto_encryption_options -> extra_options -> mongocrpytd_spawn_args

Spawn a new mongocryptd process using the mongocryptd_spawn_path and mongocryptd_spawn_args passed in through the extra auto encrypt options. Stdout and Stderr of this new process are written to /dev/null.

Returns:

  • (Integer)

    The process id of the spawned process

Raises:

  • (ArgumentError)

    Raises an exception if no encryption options have been provided

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 298

def spawn_mongocryptd
  mongocryptd_spawn_args = @options[:mongocryptd_spawn_args]
  mongocryptd_spawn_path = @options[:mongocryptd_spawn_path]

  unless mongocryptd_spawn_path
    raise ArgumentError.new(
      'Cannot spawn mongocryptd process when no ' +
      ':mongocryptd_spawn_path option is provided'
    )
  end

  if mongocryptd_spawn_path.nil? ||
     mongocryptd_spawn_args.nil? || mongocryptd_spawn_args.empty?
    raise ArgumentError.new(
      'Cannot spawn mongocryptd process when no :mongocryptd_spawn_args ' +
      'option is provided. To start mongocryptd without arguments, pass ' +
      '"--" for :mongocryptd_spawn_args'
    )
  end

  begin
    Process.spawn(
      mongocryptd_spawn_path,
      *mongocryptd_spawn_args,
      %i[out err] => '/dev/null'
    )
  rescue Errno::ENOENT => e
    raise Error::MongocryptdSpawnError.new(
      "Failed to spawn mongocryptd at the path \"#{mongocryptd_spawn_path}\" " +
      "with arguments #{mongocryptd_spawn_args}. Received error " +
      "#{e.class}: \"#{e.message}\""
    )
  end
end

#update_data_keys(updates, timeout_ms: nil) ⇒ BulkWrite::Result

Apply given requests to the key vault collection using bulk write.

Parameters:

  • requests (Array<Hash>)

    The bulk write requests.

Returns:

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 249

def update_data_keys(updates, timeout_ms: nil)
  key_vault_collection.bulk_write(updates, timeout_ms: timeout_ms)
end

#validate_key_vault_client!(key_vault_client) (private)

Raises:

  • (ArgumentError)
[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 255

def validate_key_vault_client!(key_vault_client)
  raise ArgumentError.new('The :key_vault_client option cannot be nil') unless key_vault_client

  return if key_vault_client.is_a?(Client)

  raise ArgumentError.new(
    'The :key_vault_client option must be an instance of Mongo::Client'
  )
end

#validate_key_vault_namespace!(key_vault_namespace) (private)

Raises:

  • (ArgumentError)
[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 265

def validate_key_vault_namespace!(key_vault_namespace)
  raise ArgumentError.new('The :key_vault_namespace option cannot be nil') unless key_vault_namespace

  return if key_vault_namespace.split('.').length == 2

  raise ArgumentError.new(
    "#{key_vault_namespace} is an invalid key vault namespace." +
    'The :key_vault_namespace option must be in the format database.collection'
  )
end

#with_ssl_socket(endpoint, tls_options, timeout_ms: nil) {|ssl_socket| ... } (private)

Note:

The socket is always closed when the provided block has finished executing

Provide a TLS socket to be used for KMS calls in a block API

Parameters:

  • endpoint (String)

    The URI at which to connect the TLS socket.

  • tls_options. (Hash)

    TLS options to connect to KMS provider. The options are same as for ::Mongo::Client.

Yield Parameters:

  • ssl_socket (OpenSSL::SSL::SSLSocket)

    Yields a TLS socket connected to the specified endpoint.

Raises:

[ GitHub ]

  
# File 'lib/mongo/crypt/encryption_io.rb', line 346

def with_ssl_socket(endpoint, tls_options, timeout_ms: nil)
  csot = !timeout_ms.nil?
  address = begin
    host, port = endpoint.split(':')
    port ||= 443 # All supported KMS APIs use this port by default.
    Address.new([ host, port ].join(':'))
  end
  socket_options = { ssl: true, csot: csot }.tap do |opts|
    opts[:connect_timeout] = (timeout_ms / 1_000.0) if csot
  end
  mongo_socket = address.socket(
    SOCKET_TIMEOUT,
    tls_options.merge(socket_options)
  )
  yield(mongo_socket.socket)
rescue Error::KmsError
  raise
rescue StandardError => e
  raise Error::KmsError.new("Error when connecting to KMS provider: #{e.class}: #{e.message}",
                            network_error: true)
ensure
  mongo_socket&.close
end