class Mongo::Crypt::Context

A wrapper around mongocrypt_ctx_t, which manages the state machine for encryption and decription.

This class is a superclass that defines shared methods amongst contexts that are initialized for different purposes (e.g. data key creation, encryption, explicit encryption, etc.)

@api private

Attributes

ctx_p[R]

Public Class Methods

new(mongocrypt_handle, io) click to toggle source
Create a new Context object

@param [ Mongo::Crypt::Handle ] mongocrypt_handle A handle to libmongocrypt

used to create a new context object.

@param [ ClientEncryption::IO ] io An instance of the IO class

that implements driver I/O methods required to run the
state machine.
# File lib/mongo/crypt/context.rb, line 40
def initialize(mongocrypt_handle, io)
  @mongocrypt_handle = mongocrypt_handle
  # Ideally, this level of the API wouldn't be passing around pointer
  # references between objects, so this method signature is subject to change.

  # FFI::AutoPointer uses a custom release strategy to automatically free
  # the pointer once this object goes out of scope
  @ctx_p = FFI::AutoPointer.new(
    Binding.mongocrypt_ctx_new(@mongocrypt_handle.ref),
    Binding.method(:mongocrypt_ctx_destroy)
  )

  @encryption_io = io
  @cached_azure_token = nil
end

Public Instance Methods

run_state_machine() click to toggle source

Runs the mongocrypt_ctx_t state machine and handles all I/O on behalf of libmongocrypt

@return [ BSON::Document ] A BSON document representing the outcome

of the state machine. Contents can differ depending on how the
context was initialized..

@raise [ Error::CryptError ] If the state machine enters the

:error state

This method is not currently unit tested. It is integration tested in spec/integration/explicit_encryption_spec.rb

# File lib/mongo/crypt/context.rb, line 77
def run_state_machine
  while true
    case state
    when :error
      Binding.check_ctx_status(self)
    when :ready
      # Finalize the state machine and return the result as a BSON::Document
      return Binding.ctx_finalize(self)
    when :done
      return nil
    when :need_mongo_keys
      filter = Binding.ctx_mongo_op(self)

      @encryption_io.find_keys(filter).each do |key|
        mongocrypt_feed(key) if key
      end

      mongocrypt_done
    when :need_mongo_collinfo
      filter = Binding.ctx_mongo_op(self)

      result = @encryption_io.collection_info(@db_name, filter)
      mongocrypt_feed(result) if result

      mongocrypt_done
    when :need_mongo_markings
      cmd = Binding.ctx_mongo_op(self)

      result = @encryption_io.mark_command(cmd)
      mongocrypt_feed(result)

      mongocrypt_done
    when :need_kms
      while kms_context = Binding.ctx_next_kms_ctx(self) do
        provider = Binding.kms_ctx_get_kms_provider(kms_context)
        tls_options = @mongocrypt_handle.kms_tls_options(provider)
        @encryption_io.feed_kms(kms_context, tls_options)
      end

      Binding.ctx_kms_done(self)
    when :need_kms_credentials
      Binding.ctx_provide_kms_providers(
        self,
        retrieve_kms_credentials.to_document
      )
    else
      raise Error::CryptError.new(
        "State #{state} is not supported by Mongo::Crypt::Context"
      )
    end
  end
end
state() click to toggle source

Returns the state of the mongocrypt_ctx_t

@return [ Symbol ] The context state

# File lib/mongo/crypt/context.rb, line 61
def state
  Binding.mongocrypt_ctx_state(@ctx_p)
end

Private Instance Methods

azure_access_token() click to toggle source

Returns an Azure access token, retrieving it if necessary.

@return [ String ] An Azure access token.

@raise [ Error::CryptError ] If the Azure access token could not be

retrieved.
# File lib/mongo/crypt/context.rb, line 191
def azure_access_token
  if @cached_azure_token.nil? || @cached_azure_token.expired?
    @cached_azure_token = KMS::Azure::CredentialsRetriever.fetch_access_token
  end
  @cached_azure_token.access_token
rescue KMS::CredentialsNotFound => e
  raise Error::CryptError.new(
    "Could not locate Azure credentials: #{e.class}: #{e.message}"
  )
end
gcp_access_token() click to toggle source

Retrieves a GCP access token.

@return [ String ] A GCP access token.

@raise [ Error::CryptError ] If the GCP access token could not be

# File lib/mongo/crypt/context.rb, line 177
def gcp_access_token
  KMS::GCP::CredentialsRetriever.fetch_access_token
rescue KMS::CredentialsNotFound => e
  raise Error::CryptError.new(
    "Could not locate GCP credentials: #{e.class}: #{e.message}"
  )
end
mongocrypt_done() click to toggle source

Indicate that state machine is done feeding I/O responses back to libmongocrypt

# File lib/mongo/crypt/context.rb, line 133
def mongocrypt_done
  Binding.mongocrypt_ctx_mongo_done(ctx_p)
end
mongocrypt_feed(doc) click to toggle source

Feeds the result of a Mongo operation back to libmongocrypt.

@param [ Hash ] doc BSON document to feed.

@return [ BSON::Document ] BSON document containing the result.

# File lib/mongo/crypt/context.rb, line 142
def mongocrypt_feed(doc)
  Binding.ctx_mongo_feed(self, doc)
end
retrieve_kms_credentials() click to toggle source

Retrieves KMS credentials for providers that are configured for automatic credentials retrieval.

@return [ Crypt::KMS::Credentials ] Credentials for the configured

KMS providers.
# File lib/mongo/crypt/context.rb, line 151
def retrieve_kms_credentials
  providers = {}
  if kms_providers.aws&.empty?
    begin
      aws_credentials = Mongo::Auth::Aws::CredentialsRetriever.new.credentials
    rescue Auth::Aws::CredentialsNotFound
      raise Error::CryptError.new(
        "Could not locate AWS credentials (checked environment variables, ECS and EC2 metadata)"
      )
    end
    providers[:aws] = aws_credentials.to_h
  end
  if kms_providers.gcp&.empty?
    providers[:gcp] = { access_token: gcp_access_token }
  end
  if kms_providers.azure&.empty?
    providers[:azure] = { access_token: azure_access_token }
  end
  KMS::Credentials.new(providers)
end