The default headers that are sent with every request.
The default list of options to use when parsing JSON.
The name of the header used for redirection.
The name of the header used to hold the Vault token.
The user agent for this client.
The name of the header used to hold the wrapped request ttl.
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 71 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
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 309 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 332 def class_for_request(verb) Net::HTTP.const_get(verb.to_s.capitalize) end
Perform a DELETE request. @see #request
# File lib/vault/client.rb, line 212 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 374 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 #request
# File lib/vault/client.rb, line 181 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
Perform a LIST request. @see #request
# File lib/vault/client.rb, line 187 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 #request
# File lib/vault/client.rb, line 206 def patch(path, data, headers = {}) request(:patch, path, data, headers) end
Perform a POST request. @see #request
# File lib/vault/client.rb, line 194 def post(path, data = {}, headers = {}) request(:post, path, data, headers) end
Perform a PUT request. @see #request
# File lib/vault/client.rb, line 200 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 235 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 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 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 175 def same_options?(opts) options.hash == opts.hash 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 359 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 344 def to_query_string(hash) hash.map do |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" end.join('&')[/.+/] 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 407 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 166 def with_token(token) client = self.dup client.token = token return yield client if block_given? return nil end
# File lib/vault/client.rb, line 82 def pool @lock.synchronize do return @nhp if @nhp @nhp = PersistentHTTP.new("vault-ruby", nil, pool_size) if hostname @nhp.hostname = hostname end 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