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 181

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) ⇒ Hash

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:

  • (Hash)

    The collection information

[ 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)
    .first
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 191

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 159

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 196

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 202

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 207

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 272

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, timeout_ms: nil) ⇒ Hash

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

Parameters:

  • cmd (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:

  • (Hash)

    The marked command

[ GitHub ]

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

def mark_command(cmd, 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
  }

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

    spawn_mongocryptd
    response = @mongocryptd_client.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 213

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 292

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 243

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 249

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 259

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 340

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