module Vault::Rails

Constants

DEFAULT_ENCODING

The default encoding.

@return [String]

DEV_PREFIX
DEV_WARNING

The warning string to print when running in development mode.

SERIALIZERS

The list of serializers.

@return [Hash<Symbol, Module>]

VERSION

Attributes

client[R]

API client object based off the configured options in {Configurable}.

@return [Vault::Client]

Public Class Methods

decrypt(path, key, ciphertext, client: self.client, context: nil) click to toggle source

Decrypt the given ciphertext data using the provided mount and key.

@param [String] path

the mount point

@param [String] key

the key to decrypt at

@param [String] ciphertext

the ciphertext to decrypt

@param [Vault::Client] client

the Vault client to use

@return [String]

the decrypted plaintext text
# File lib/vault/rails.rb, line 111
def decrypt(path, key, ciphertext, client: self.client, context: nil)
  if ciphertext.blank?
    return ciphertext
  end

  path = path.to_s if !path.is_a?(String)
  key  = key.to_s if !key.is_a?(String)

  with_retries do
    if self.enabled?
      result = self.vault_decrypt(path, key, ciphertext, client: client, context: context)
    else
      result = self.memory_decrypt(path, key, ciphertext, client: client, context: context)
    end

    return self.force_encoding(result)
  end
end
encrypt(path, key, plaintext, client: self.client, context: nil) click to toggle source

Encrypt the given plaintext data using the provided mount and key.

@param [String] path

the mount point

@param [String] key

the key to encrypt at

@param [String] plaintext

the plaintext to encrypt

@param [Vault::Client] client

the Vault client to use

@return [String]

the encrypted cipher text
# File lib/vault/rails.rb, line 79
def encrypt(path, key, plaintext, client: self.client, context: nil)
  if plaintext.blank?
    return plaintext
  end

  path = path.to_s if !path.is_a?(String)
  key  = key.to_s if !key.is_a?(String)

  with_retries do
    if self.enabled?
      result = self.vault_encrypt(path, key, plaintext, client: client, context: context)
    else
      result = self.memory_encrypt(path, key, plaintext, client: client, context: context)
    end

    return self.force_encoding(result)
  end
end
method_missing(m, *args, &block) click to toggle source

Delegate all methods to the client object, essentially making the module object behave like a {Vault::Client}.

Calls superclass method
# File lib/vault/rails.rb, line 53
def method_missing(m, *args, &block)
  if client.respond_to?(m)
    client.public_send(m, *args, &block)
  else
    super
  end
end
respond_to_missing?(m, include_private = false) click to toggle source

Delegating ‘respond_to` to the {Vault::Client}.

Calls superclass method
# File lib/vault/rails.rb, line 62
def respond_to_missing?(m, include_private = false)
  client.respond_to?(m, include_private) || super
end
serializer_for(key) click to toggle source

Get the serializer that corresponds to the given key. If the key does not correspond to a known serializer, an exception will be raised.

@param [#to_sym] key

the name of the serializer

@return [~Serializer]

# File lib/vault/rails.rb, line 137
def serializer_for(key)
  key = key.to_sym if !key.is_a?(Symbol)

  if serializer = SERIALIZERS[key]
    return serializer
  else
    raise Vault::Rails::UnknownSerializerError.new(key)
  end
end
setup!() click to toggle source
# File lib/vault/rails.rb, line 40
def setup!
  Vault.setup!

  @client = Vault.client
  @client.class.instance_eval do
    include Vault::Rails::Configurable
  end

  self
end
transform_decode(ciphertext, opts={}) click to toggle source
# File lib/vault/rails.rb, line 160
def transform_decode(ciphertext, opts={})
  return ciphertext if ciphertext&.empty?
  request_opts = {}
  request_opts[:value] = ciphertext

  if opts[:transformation]
    request_opts[:transformation] = opts[:transformation]
  end

  role_name = transform_role_name(opts)
  puts request_opts
  client.transform.decode(role_name: role_name, **request_opts)
end
transform_encode(plaintext, opts={}) click to toggle source
# File lib/vault/rails.rb, line 147
def transform_encode(plaintext, opts={})
  return plaintext if plaintext&.empty?
  request_opts = {}
  request_opts[:value] = plaintext

  if opts[:transformation]
    request_opts[:transformation] = opts[:transformation]
  end

  role_name = transform_role_name(opts)
  client.transform.encode(role_name: role_name, **request_opts)
end

Protected Class Methods

force_encoding(str) click to toggle source

Forces the encoding into the default Rails encoding and returns the newly encoded string. @return [String]

# File lib/vault/rails.rb, line 247
def force_encoding(str)
  encoding = ::Rails.application.config.encoding || DEFAULT_ENCODING
  str.force_encoding(encoding).encode(encoding)
end
memory_decrypt(path, key, ciphertext, client: , context: nil) click to toggle source

Perform in-memory decryption. This is useful for testing and development.

# File lib/vault/rails.rb, line 190
def memory_decrypt(path, key, ciphertext, client: , context: nil)
  log_warning(DEV_WARNING) if self.in_memory_warnings_enabled?

  return nil if ciphertext.nil?

  raise Vault::Rails::InvalidCiphertext.new(ciphertext) if !ciphertext.start_with?(DEV_PREFIX)
  data = ciphertext[DEV_PREFIX.length..-1]

  cipher = OpenSSL::Cipher::AES.new(128, :CBC)
  cipher.decrypt
  cipher.key = memory_key_for(path, key, context: context)
  return cipher.update(Base64.strict_decode64(data)) + cipher.final
end
memory_encrypt(path, key, plaintext, client: , context: nil) click to toggle source

Perform in-memory encryption. This is useful for testing and development.

# File lib/vault/rails.rb, line 178
def memory_encrypt(path, key, plaintext, client: , context: nil)
  log_warning(DEV_WARNING) if self.in_memory_warnings_enabled?

  return nil if plaintext.nil?

  cipher = OpenSSL::Cipher::AES.new(128, :CBC)
  cipher.encrypt
  cipher.key = memory_key_for(path, key, context: context)
  return DEV_PREFIX + Base64.strict_encode64(cipher.update(plaintext) + cipher.final)
end
memory_key_for(path, key, context: nil) click to toggle source

The symmetric key for the given params. @return [String]

# File lib/vault/rails.rb, line 206
def memory_key_for(path, key, context: nil)
  md5 = OpenSSL::Digest::MD5.new
  md5 << path
  md5 << key
  md5 << context if context
  md5.digest
end
vault_decrypt(path, key, ciphertext, client: , context: nil) click to toggle source

Perform decryption using Vault. This will raise exceptions if Vault is unavailable.

# File lib/vault/rails.rb, line 231
def vault_decrypt(path, key, ciphertext, client: , context: nil)
  return nil if ciphertext.nil?

  route = File.join(path, "decrypt", key)

  data = { ciphertext: ciphertext }
  data[:context] = Base64.strict_encode64(context) if context

  secret = client.logical.write(route, data)

  return Base64.strict_decode64(secret.data[:plaintext])
end
vault_encrypt(path, key, plaintext, client: , context: nil) click to toggle source

Perform encryption using Vault. This will raise exceptions if Vault is unavailable.

# File lib/vault/rails.rb, line 216
def vault_encrypt(path, key, plaintext, client: , context: nil)
  return nil if plaintext.nil?

  route = File.join(path, "encrypt", key)

  data = { plaintext: Base64.strict_encode64(plaintext) }
  data[:context] = Base64.strict_encode64(context) if context

  secret = client.logical.write(route, data)

  return secret.data[:ciphertext]
end

Private Class Methods

log_warning(msg) click to toggle source
# File lib/vault/rails.rb, line 272
def log_warning(msg)
  if defined?(::Rails) && ::Rails.logger != nil
    ::Rails.logger.warn { msg }
  end
end
transform_role_name(opts) click to toggle source
# File lib/vault/rails.rb, line 278
def transform_role_name(opts)
  opts[:role] || self.default_role_name || self.application
end
with_retries(client = self.client) { || ... } click to toggle source
# File lib/vault/rails.rb, line 254
def with_retries(client = self.client, &block)
  exceptions = [Vault::HTTPConnectionError, Vault::HTTPServerError, Vault::MissingRequiredStateError]
  options = {
    attempts: self.retry_attempts,
    base:     self.retry_base,
    max_wait: self.retry_max_wait,
  }

  client.with_retries(*exceptions, options) do |i, e|
    if !e.nil?
      log_warning "[vault-rails] (#{i}) An error occurred when trying to " \
        "communicate with Vault: #{e.message}"
    end

    yield
  end
end