class Vault::Client
Constants
- DEFAULT_HEADERS
The default headers that are sent with every request.
- JSON_PARSE_OPTIONS
The default list of options to use when parsing JSON.
- LOCATION_HEADER
The name of the header used for redirection.
- NAMESPACE_HEADER
The name of the header used to hold the
Namespace
.- RESCUED_EXCEPTIONS
- TOKEN_HEADER
The name of the header used to hold the
Vault
token.- USER_AGENT
The user agent for this client.
- WRAP_TTL_HEADER
The name of the header used to hold the wrapped request ttl.
Public Class Methods
Create a new Client
with the given options. Any options given take precedence over the default options.
@return [Vault::Client]
# File lib/vault/client.rb, line 74 def initialize(options = {}) # Use any options given, but fall back to the defaults set on the module Vault::Configurable.keys.each do |key| value = options.key?(key) ? options[key] : Defaults.public_send(key) instance_variable_set(:"@#{key}", value) end @lock = Mutex.new @nhp = nil end
Public Instance Methods
A proxy to the {AppRole} methods. @return [AppRole]
# File lib/vault/api/approle.rb, line 12 def approle @approle ||= AppRole.new(self) end
A proxy to the {Auth} methods. @return [Auth]
# File lib/vault/api/auth.rb, line 10 def auth @auth ||= Authenticate.new(self) end
A proxy to the {AuthTLS} methods. @return [AuthTLS]
# File lib/vault/api/auth_tls.rb, line 12 def auth_tls @auth_tls ||= AuthTLS.new(self) end
A proxy to the {AuthToken} methods. @return [AuthToken]
# File lib/vault/api/auth_token.rb, line 12 def auth_token @auth_token ||= AuthToken.new(self) end
Construct a URL from the given verb and path. If the request is a GET or DELETE request, the params are assumed to be query params are are converted as such using {Client#to_query_string}.
If the path is relative, it is merged with the {Defaults.address} attribute. If the path is absolute, it is converted to a URI object and returned.
@param [Symbol] verb
the lowercase HTTP verb (e.g. :+get+)
@param [String] path
the absolute or relative HTTP path (url) to get
@param [Hash] params
the list of params to build the URI with (for GET and DELETE requests)
@return [URI]
# File lib/vault/client.rb, line 323 def build_uri(verb, path, params = {}) # Add any query string parameters if [:delete, :get].include?(verb) path = [path, to_query_string(params)].compact.join("?") end # Parse the URI uri = URI.parse(path) # Don't merge absolute URLs uri = URI.parse(File.join(address, path)) unless uri.absolute? # Return the URI object uri end
Helper method to get the corresponding {Net::HTTP} class from the given HTTP verb.
@param [#to_s] verb
the HTTP verb to create a class from
@return [Class]
# File lib/vault/client.rb, line 346 def class_for_request(verb) Net::HTTP.const_get(verb.to_s.capitalize) end
Perform a DELETE request. @see Client#request
# File lib/vault/client.rb, line 217 def delete(path, params = {}, headers = {}) request(:delete, path, params, headers) end
Raise a response error, extracting as much information from the server's response as possible.
@raise [HTTPError]
@param [HTTP::Message] response
the response object from the request
# File lib/vault/client.rb, line 388 def error(response) if response.body && response.body.match("missing client token") raise MissingTokenError end # Use the correct exception class case response when Net::HTTPClientError klass = HTTPClientError when Net::HTTPServerError klass = HTTPServerError else klass = HTTPError end if (response.content_type || '').include?("json") # Attempt to parse the error as JSON begin json = JSON.parse(response.body, JSON_PARSE_OPTIONS) if json[:errors] raise klass.new(address, response, json[:errors]) end rescue JSON::ParserError; end end raise klass.new(address, response, [response.body]) end
Perform a GET request. @see Client#request
# File lib/vault/client.rb, line 186 def get(path, params = {}, headers = {}) request(:get, path, params, headers) end
Gets help for the given path.
@example
Vault.help("secret") #=> #<Vault::Help help="..." see_also="...">
@param [String] path
the path to get help for
@return [Help]
# File lib/vault/api/help.rb, line 28 def help(path) json = self.get("/v1/#{EncodePath.encode_path(path)}", help: 1) return Help.decode(json) end
A proxy to the {KV} methods. @return [KV]
# File lib/vault/api/kv.rb, line 10 def kv(mount) KV.new(self, mount) end
Perform a LIST request. @see Client#request
# File lib/vault/client.rb, line 192 def list(path, params = {}, headers = {}) params = params.merge(list: true) request(:get, path, params, headers) end
A proxy to the {Logical} methods. @return [Logical]
# File lib/vault/api/logical.rb, line 10 def logical @logical ||= Logical.new(self) end
Perform a PATCH request. @see Client#request
# File lib/vault/client.rb, line 211 def patch(path, data, headers = {}) request(:patch, path, data, headers) end
Perform a POST request. @see Client#request
# File lib/vault/client.rb, line 199 def post(path, data = {}, headers = {}) request(:post, path, data, headers) end
Perform a PUT request. @see Client#request
# File lib/vault/client.rb, line 205 def put(path, data, headers = {}) request(:put, path, data, headers) end
Make an HTTP request with the given verb, data, params, and headers. If the response has a return type of JSON, the JSON is automatically parsed and returned as a hash; otherwise it is returned as a string.
@raise [HTTPError]
if the request is not an HTTP 200 OK
@param [Symbol] verb
the lowercase symbol of the HTTP verb (e.g. :get, :delete)
@param [String] path
the absolute or relative path from {Defaults.address} to make the request against
@param [#read, Hash, nil] data
the data to use (varies based on the +verb+)
@param [Hash] headers
the list of headers to use
@return [String, Hash]
the response body
# File lib/vault/client.rb, line 240 def request(verb, path, data = {}, headers = {}) # Build the URI and request object from the given information uri = build_uri(verb, path, data) request = class_for_request(verb).new(uri.request_uri) if uri.userinfo() request.basic_auth uri.user, uri.password end if proxy_address and uri.scheme.downcase == "https" raise SecurityError, "no direct https connection to vault" end # Get a list of headers headers = DEFAULT_HEADERS.merge(headers) # Add the Vault token header - users could still override this on a # per-request basis if !token.nil? headers[TOKEN_HEADER] ||= token end # Add the Vault Namespace header - users could still override this on a # per-request basis if !namespace.nil? headers[NAMESPACE_HEADER] ||= namespace end # Add headers headers.each do |key, value| request.add_field(key, value) end # Setup PATCH/POST/PUT if [:patch, :post, :put].include?(verb) if data.respond_to?(:read) request.content_length = data.size request.body_stream = data elsif data.is_a?(Hash) request.form_data = data else request.body = data end end begin # Create a connection using the block form, which will ensure the socket # is properly closed in the event of an error. response = pool.request(uri, request) case response when Net::HTTPRedirection # On a redirect of a GET or HEAD request, the URL already contains # the data as query string parameters. if [:head, :get].include?(verb) data = {} end request(verb, response[LOCATION_HEADER], data, headers) when Net::HTTPSuccess success(response) else error(response) end rescue *RESCUED_EXCEPTIONS => e raise HTTPConnectionError.new(address, e) end end
Determine if the given options are the same as ours. @return [true, false]
# File lib/vault/client.rb, line 180 def same_options?(opts) options.hash == opts.hash end
Shutdown any open pool connections. Pool will be recreated upon next request.
# File lib/vault/client.rb, line 161 def shutdown @nhp.shutdown() @nhp = nil end
Parse the response object and manipulate the result based on the given Content-Type
header. For now, this method only parses JSON, but it could be expanded in the future to accept other content types.
@param [HTTP::Message] response
the response object from the request
@return [String, Hash]
the parsed response, as an object
# File lib/vault/client.rb, line 373 def success(response) if response.body && (response.content_type || '').include?("json") JSON.parse(response.body, JSON_PARSE_OPTIONS) else response.body end end
A proxy to the {Sys} methods. @return [Sys]
# File lib/vault/api/sys.rb, line 9 def sys @sys ||= Sys.new(self) end
Convert the given hash to a list of query string parameters. Each key and value in the hash is URI-escaped for safety.
@param [Hash] hash
the hash to create the query string from
@return [String, nil]
the query string as a string, or +nil+ if there are no params
# File lib/vault/client.rb, line 358 def to_query_string(hash) hash.map do |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" end.join('&')[/.+/] end
A proxy to the {Transform} methods. @return [Transform]
# File lib/vault/api/transform.rb, line 8 def transform @transform ||= Transform.new(self) end
Execute the given block with retries and exponential backoff.
@param [Array<Exception>] rescued
the list of exceptions to rescue
# File lib/vault/client.rb, line 421 def with_retries(*rescued, &block) options = rescued.last.is_a?(Hash) ? rescued.pop : {} exception = nil retries = 0 rescued = Defaults::RETRIED_EXCEPTIONS if rescued.empty? max_attempts = options[:attempts] || Defaults::RETRY_ATTEMPTS backoff_base = options[:base] || Defaults::RETRY_BASE backoff_max = options[:max_wait] || Defaults::RETRY_MAX_WAIT begin return yield retries, exception rescue *rescued => e exception = e retries += 1 raise if retries > max_attempts # Calculate the exponential backoff combined with an element of # randomness. backoff = [backoff_base * (2 ** (retries - 1)), backoff_max].min backoff = backoff * (0.5 * (1 + Kernel.rand)) # Ensure we are sleeping at least the minimum interval. backoff = [backoff_base, backoff].max # Exponential backoff. Kernel.sleep(backoff) # Now retry retry end end
Creates and yields a new client object with the given token. This may be used safely in a threadsafe manner because the original client remains unchanged. The value of the block is returned.
@yield [Vault::Client]
# File lib/vault/client.rb, line 171 def with_token(token) client = self.dup client.token = token return yield client if block_given? return nil end
Private Instance Methods
# File lib/vault/client.rb, line 85 def pool @lock.synchronize do return @nhp if @nhp @nhp = PersistentHTTP.new("vault-ruby", nil, pool_size) if proxy_address proxy_uri = URI.parse "http://#{proxy_address}" proxy_uri.port = proxy_port if proxy_port if proxy_username proxy_uri.user = proxy_username proxy_uri.password = proxy_password end @nhp.proxy = proxy_uri end # Use a custom open timeout if open_timeout || timeout @nhp.open_timeout = (open_timeout || timeout).to_i end # Use a custom read timeout if read_timeout || timeout @nhp.read_timeout = (read_timeout || timeout).to_i end @nhp.verify_mode = OpenSSL::SSL::VERIFY_PEER # Vault requires TLS1.2 @nhp.ssl_version = "TLSv1_2" # Only use secure ciphers @nhp.ciphers = ssl_ciphers # Custom pem files, no problem! pem = ssl_pem_contents || (ssl_pem_file ? File.read(ssl_pem_file) : nil) if pem @nhp.cert = OpenSSL::X509::Certificate.new(pem) @nhp.key = OpenSSL::PKey::RSA.new(pem, ssl_pem_passphrase) end # Use custom CA cert for verification if ssl_ca_cert @nhp.ca_file = ssl_ca_cert end # Use custom CA path that contains CA certs if ssl_ca_path @nhp.ca_path = ssl_ca_path end if ssl_cert_store @nhp.cert_store = ssl_cert_store end # Naughty, naughty, naughty! Don't blame me when someone hops in # and executes a MITM attack! if !ssl_verify @nhp.verify_mode = OpenSSL::SSL::VERIFY_NONE end # Use custom timeout for connecting and verifying via SSL if ssl_timeout || timeout @nhp.ssl_timeout = (ssl_timeout || timeout).to_i end @nhp end end