Class: CipherStash::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/cipherstash/client.rb,
lib/cipherstash/client/rpc.rb,
lib/cipherstash/client/error.rb,
lib/cipherstash/client/console.rb,
lib/cipherstash/client/metrics.rb,
lib/cipherstash/client/profile.rb,
lib/cipherstash/client/creds_proxy.rb,
lib/cipherstash/client/hash_helper.rb,
lib/cipherstash/client/aws_federated_credentials.rb,
lib/cipherstash/client/auth0_device_code_credentials.rb,
lib/cipherstash/client/ordered_string_test_generator.rb,
lib/cipherstash/client/console_access_key_credentials.rb

Overview

An all-in-one client for the CipherStash encrypted searchable data store.

This is the only thing you should ever need to explicitly instantiate in order to use CipherStash from Ruby.

Defined Under Namespace

Classes: Error, Metrics

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(profileName: Unspecified, logger: Unspecified, metrics: Metrics::Null.new, rpc_class: RPC, **opts) ⇒ Client

Create a new CipherStash client.

No options are necessary in the common case. The default profile will be loaded from ~/.cipherstash, modified by any CS_* environment variables may be set. Options passed to this constructor directly will further override profile values or environment variables.

Examples:

Load profile from disk and/or environment

cs = CipherStash::Client.new

Specify a custom logger

logger = Logger.new
logger.level = Logger::DEBUG

cs = CipherStash::Client.new(logger: logger)

Load specified profile by name

# This loads the given profile, but options will
# still be overridden by env vars, if present
cs = CipherStash::Client.new(profileName: "ruby-client-profile")

Load a profile and set a different access key

cs = CipherStash::Client.new(profileName: "example", idpClientSecret: "CSAKSOMETHING.SOMETHING")

Parameters:

  • profileName (Hash) (defaults to: Unspecified)

    a customizable set of options

  • logger (Hash) (defaults to: Unspecified)

    a customizable set of options

  • rpc_class (Hash) (defaults to: RPC)

    a customizable set of options

  • metrics (Hash) (defaults to: Metrics::Null.new)

    a customizable set of options

  • opts (Hash)

    a customizable set of options

Options Hash (profileName:):

  • load (String)

    a specific profile, rather than letting the default client configuration process select a profile for you. Note that the profile you specify can still have its settings overridden by any relevant environment variables or configuration options passed to the client.

Options Hash (logger:):

  • specify (Logger)

    a custom logger. If not provided, only warnings and errors will be printed to stderr.

Options Hash (metrics:):

  • somewhere (CipherStash::Client::Metrics)

    to collect metrics about this client's operation. If not provided, no metrics will be collected. For more information on metrics and how they work, see the documentation for CipherStash::Client::Metrics.

Options Hash (rpc_class:):

  • a (Class)

    class to use in place of CipherStash::Client::RPC. This option is used internally for unit testing and is not intended for regular usage. Defaults to CipherStash::Client::RPC.

Options Hash (**opts):

  • additional (Hash<Symbol, String | Integer | Boolean>)

    configuration options for the client, which override the values of the corresponding configuration items in the loaded profile, or environment variables. For the full list of supported option names, see the Client Configuration reference.

Raises:

See Also:


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/cipherstash/client.rb', line 100

def initialize(profileName: Unspecified, logger: Unspecified, metrics: Metrics::Null.new, rpc_class: RPC, **opts)
  @logger = if logger == Unspecified
              self.class.default_logger
            else
              logger
            end

  if (leftovers = opts.keys - Client.client_options) != []
    raise Error::InvalidProfileError, "Unsupported configuration option(s) found: #{leftovers.inspect}"
  end

  @profile = Profile.load(profileName == Unspecified ? nil : profileName, @logger, **opts)
  @metrics = metrics
  @metrics.created
  @rpc = rpc_class.new(@profile, @logger, @metrics)
end

Class Method Details

.client_optionsArray<Symbol>

Enumerate the list of valid configuration options for a CipherStash client.

This is mostly of use to libraries that "wrap" CipherStash::Client (such as active_stash), so they can more smoothly handle all configuration parameters without needing to hard-code big long lists.

Returns:

  • (Array<Symbol>)

    the list of valid profile configuration options. The option names are in the "canonical" name format, as listed in the Client Configuration reference, such as :idpClientSecret.


30
31
32
# File 'lib/cipherstash/client.rb', line 30

def self.client_options
  Profile.profile_options
end

.login(workspace: nil, profile_name: nil, logger: default_logger) ⇒ TrueClass

Log into a CipherStash workspace.

If workspace is given, a profile will be created for that workspace. This is useful for logging into a workspace for the first time.

If a workspace is not provided, this command will load an existing profile and then log in using that profile. The profile name defaults to "default", but can be overridden with the CS_PROFILE_NAME env var or the defaultProfile opt in {cs_config_path}/config.json.

Returns:

  • (TrueClass)

Raises:


49
50
51
52
# File 'lib/cipherstash/client.rb', line 49

def self.(workspace: nil, profile_name: nil, logger: default_logger)
  Profile.(workspace: workspace, profile_name: profile_name, logger: logger)
  true
end

Instance Method Details

#access_keysArray<CipherStash::AccessKey>

Get the list of currently-extant access keys for the workspace of this client.

Returns:

Raises:


235
236
237
238
239
# File 'lib/cipherstash/client.rb', line 235

def access_keys
  @metrics.measure_client_call("access_keys") do
    console.access_key_list(@profile.workspace).map { |ak| AccessKey.new(symbolize_keys(ak)) }
  end
end

#collection(name) ⇒ CipherStash::Collection

Load an existing collection from the data store by name.

Parameters:

  • name (String)

    the name of the collection to load.

Returns:

Raises:


127
128
129
130
131
132
133
134
# File 'lib/cipherstash/client.rb', line 127

def collection(name)
  @metrics.measure_client_call("collection") do
    @rpc.collection_info(name)
  end
rescue ::GRPC::Core::StatusCodes => ex
  @logger.error("CipherStash::Client#collection") { "Unhandled GRPC error!  Please report this as a bug!  #{ex.message} (#{ex.class})" }
  raise
end

#collectionsArray<CipherStash::Collection>

Load all collections in the data store.

Returns:

Raises:


144
145
146
147
148
149
150
151
# File 'lib/cipherstash/client.rb', line 144

def collections
  @metrics.measure_client_call("collections") do
    @rpc.collection_list
  end
rescue ::GRPC::Core::StatusCodes => ex
  @logger.error("CipherStash::Client#collections") { "Unhandled GRPC error!  Please report this as a bug!  #{ex.message} (#{ex.class})" }
  raise
end

#create_access_key(name) ⇒ CipherStash::AccessKey

Create a new access key for the workspace of this client.

An access key is a secret, long-term credential which allows non-interactive CipherStash clients to access a CipherStash workspace.

Parameters:

  • name (String)

    a unique name to associate with this access key. Set it to something suitably descriptive, so you know which access key is which.

Returns:

Raises:


223
224
225
226
227
# File 'lib/cipherstash/client.rb', line 223

def create_access_key(name)
  @metrics.measure_client_call("create_access_key") do
    AccessKey.new(symbolize_keys(console.create_access_key(name, @profile.workspace)))
  end
end

#create_collection(name, schema) ⇒ TrueClass

Create a new collection the data store.

Parameters:

  • name (String)

    the name of the collection to create.

  • schema (Hash<String, Object>)

    a description of the structure of the data and indexes in the collection. Is a Ruby nested hash with a structure identical to that described in the fine manual.

Returns:

  • (TrueClass)

Raises:

  • (CipherStash::Client::Error::CollectionCreationFailure)

    if the collection could not be loaded for some reason, such as if the collection did not exist.

  • (CipherStash::Client::Error::EncryptionFailure)

    if there was a problem while encrypting the collection or index metadata.

  • (CipherStash::Client::Error::DecryptionFailure)

    if there was a problem decrypting the naming key.

  • (CipherStash::Client::Error::InvalidSchemaError)

    if there was a problem with the format of the schema.


170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/cipherstash/client.rb', line 170

def create_collection(name, schema)
  @metrics.measure_client_call("create_collection") do
     = {
      name: name,
      recordType: schema.fetch("type", {}),
    }

    indexes = schema.fetch("indexes", {}).map do |idx_name, idx_settings|
      # New match, dynamic-match, and field-dynamic-match indexes should use filter indexes.
      # ORE match indexes require specifically using a *-ore-match index.
      idx_settings["kind"] = case idx_settings["kind"]
      when "match"
        "filter-match"
      when "dynamic-match"
        "dynamic-filter-match"
      when "field-dynamic-match"
        "field-dynamic-filter-match"
      else
        idx_settings["kind"]
      end

      Index.subclass_from_kind(idx_settings["kind"]).settings(idx_name, idx_settings, schema)
    end

    begin
      @rpc.create_collection(name, , indexes)
    rescue CipherStash::Client::Error::CollectionCreateFailure => ex
      if ::GRPC::AlreadyExists === ex.cause
        migrate_collection(name, , indexes)
      else
        raise
      end
    end

    true
  end
rescue ::GRPC::Core::StatusCodes => ex
  @logger.error("CipherStash::Client#create_collection") { "Unhandled GRPC error!  Please report this as a bug!  #{ex.message} (#{ex.class})" }
  raise
end

#delete_access_key(name) ⇒ Boolean

Delete an access key from the workspace of this client.

Deleting an access key prevents anyone who possesses the access key from being able to get a new (short-lived) access token. An existing access token may still allow access to the data-service for as long as that access token is still valid (up to 15 minutes).

Returns:

  • (Boolean)

    true if the access key was deleted, false if the access key did not exist.

Raises:


250
251
252
253
254
# File 'lib/cipherstash/client.rb', line 250

def delete_access_key(name)
  @metrics.measure_client_call("delete_access_key") do
    console.delete_access_key(name, @profile.workspace)
  end
end

#generate_naming_keyString

Generate an (encrypted) "naming key" from the wrapping key configured for this client.

The naming key is used to deterministically obscure the actual name of the collections in a workspace. It ensures that the client can ask for a collection "by name", while not disclosing that name to the server. A naming key should typically be configured once when a workspace is first created, and then not changed thereafter. This method is only necessary when configuring a workspace that doesn't use CipherStash-generated keys.

Returns:

  • (String)

264
265
266
267
268
# File 'lib/cipherstash/client.rb', line 264

def generate_naming_key
  @metrics.measure_client_call("generate_naming_key") do
    @profile.generate_naming_key
  end
end