class Confidant::Client

The Confidant Client implementation

Constants

TIME_FORMAT
TOKEN_SKEW_SECONDS

Attributes

config[RW]

Public Class Methods

new(configurator) click to toggle source

Initialize with a configurator, which is an instance of Confidant::Configurator

# File lib/confidant/client.rb, line 18
def initialize(configurator)
  @config = configurator.config
  @kms = Aws::KMS::Client.new(region: config[:region])
  @suppress_errors = false
end

Public Instance Methods

get_service(service = nil) click to toggle source

Return a Hash of credentials from the confidant API for service, either explicitly-provided, or from config.

# File lib/confidant/client.rb, line 26
def get_service(service = nil)
  log.debug "Requesting #{api_service_url(service_name(service))} " \
            "as user #{api_user}"
  response = RestClient::Request.execute(
    method: :get,
    url: api_service_url(service_name(service)),
    user: api_user,
    password: generate_token,
    headers: {
      user_agent: RestClient::Platform.default_user_agent.prepend(
        "confidant-client/#{Confidant::VERSION} "
      )
    }
  )

  JSON.parse(response.body)
rescue => ex
  Confidant.log_exception(self, ex)
  @suppress_errors ? api_error_response : raise
end
suppress_errors(enable = true) click to toggle source

The Python client suppresses API errors, returning { result: false } instead. Mimic this behavior based on the truthiness of enable. This is generally only called from Confidant::CLI

# File lib/confidant/client.rb, line 51
def suppress_errors(enable = true)
  @suppress_errors = enable
  true
end

Private Instance Methods

api_error_response() click to toggle source

The falsey response to return when @suppress_errors is true, rather than raising exceptions.

# File lib/confidant/client.rb, line 93
def api_error_response
  { 'result' => 'false' }
end
api_service_url(service) click to toggle source

The URL to get credentials for service from the Confidant server.

# File lib/confidant/client.rb, line 86
def api_service_url(service)
  format('%s/v1/services/%s', @config[:url], service)
end
api_user() click to toggle source

Return the name of the user that will connect to the confidant API TODO(v1-auth-support): Support v1-style user names.

# File lib/confidant/client.rb, line 76
def api_user
  format(
    '%s/%s/%s',
    @config[:token_version],
    @config[:user_type],
    @config[:from]
  )
end
generate_token() click to toggle source

Return an auth token for the confidant service, encrypted via KMS.

# File lib/confidant/client.rb, line 115
def generate_token
  # TODO(v1-auth-support): Handle the different encryption_context
  if @config[:token_version].to_i != 2
    raise ConfigurationError,
          'This client only supports KMS v2 auth tokens.'
  end

  encrypt_params = {
    key_id: @config[:auth_key],
    plaintext: token_payload,
    encryption_context: {
      to: @config[:to],
      from: @config[:from],
      user_type: @config[:user_type]
    }
  }

  log.debug "Asking KMS to encrypt: #{encrypt_params}"
  resp = @kms.encrypt(encrypt_params)
  Base64.strict_encode64(resp.ciphertext_blob)
end
service_name(service = nil) click to toggle source

Return the name of the service for which we should fetch credentials from the confidant API. Returns service if provided, or the config value at @config[:service] Raises ConfigurationError if no service was provided or configured.

# File lib/confidant/client.rb, line 64
def service_name(service = nil)
  return service unless service.nil?
  if @config[:get_service] && @config[:get_service][:service]
    return @config[:get_service][:service]
  end
  raise ConfigurationError,
        'Service name must be specified, or provided in config as ' \
        '{get_service: { service: \'my-service\' }'
end
token_payload() click to toggle source

The content of a confidant auth token payload, to be encrypted by KMS.

# File lib/confidant/client.rb, line 99
def token_payload
  now = Time.now.utc

  start_time = (now - TOKEN_SKEW_SECONDS)

  end_time = (
    now - TOKEN_SKEW_SECONDS +
    (@config[:token_lifetime].to_i * 60)
  )

  { not_before: start_time.strftime(TIME_FORMAT),
    not_after: end_time.strftime(TIME_FORMAT) }.to_json
end