vault_ruby_client 0.18.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +287 -0
- data/LICENSE +364 -0
- data/README.md +223 -0
- data/lib/vault/api/approle.rb +221 -0
- data/lib/vault/api/auth.rb +324 -0
- data/lib/vault/api/auth_tls.rb +95 -0
- data/lib/vault/api/auth_token.rb +245 -0
- data/lib/vault/api/help.rb +36 -0
- data/lib/vault/api/kv.rb +230 -0
- data/lib/vault/api/logical.rb +153 -0
- data/lib/vault/api/secret.rb +171 -0
- data/lib/vault/api/sys/audit.rb +94 -0
- data/lib/vault/api/sys/auth.rb +119 -0
- data/lib/vault/api/sys/health.rb +66 -0
- data/lib/vault/api/sys/init.rb +86 -0
- data/lib/vault/api/sys/leader.rb +51 -0
- data/lib/vault/api/sys/lease.rb +52 -0
- data/lib/vault/api/sys/mount.rb +165 -0
- data/lib/vault/api/sys/namespace.rb +86 -0
- data/lib/vault/api/sys/policy.rb +95 -0
- data/lib/vault/api/sys/quota.rb +110 -0
- data/lib/vault/api/sys/seal.rb +84 -0
- data/lib/vault/api/sys.rb +30 -0
- data/lib/vault/api/transform/alphabet.rb +46 -0
- data/lib/vault/api/transform/role.rb +45 -0
- data/lib/vault/api/transform/template.rb +57 -0
- data/lib/vault/api/transform/transformation.rb +64 -0
- data/lib/vault/api/transform.rb +32 -0
- data/lib/vault/api.rb +17 -0
- data/lib/vault/client.rb +460 -0
- data/lib/vault/configurable.rb +53 -0
- data/lib/vault/defaults.rb +218 -0
- data/lib/vault/encode.rb +22 -0
- data/lib/vault/errors.rb +87 -0
- data/lib/vault/persistent/connection.rb +45 -0
- data/lib/vault/persistent/pool.rb +51 -0
- data/lib/vault/persistent/timed_stack_multi.rb +73 -0
- data/lib/vault/persistent.rb +1161 -0
- data/lib/vault/request.rb +47 -0
- data/lib/vault/response.rb +92 -0
- data/lib/vault/vendor/connection_pool/timed_stack.rb +181 -0
- data/lib/vault/vendor/connection_pool/version.rb +8 -0
- data/lib/vault/vendor/connection_pool.rb +153 -0
- data/lib/vault/version.rb +6 -0
- data/lib/vault_ruby_client.rb +53 -0
- metadata +158 -0
@@ -0,0 +1,218 @@
|
|
1
|
+
# Copyright (c) HashiCorp, Inc.
|
2
|
+
# SPDX-License-Identifier: MPL-2.0
|
3
|
+
|
4
|
+
require "pathname"
|
5
|
+
require "base64"
|
6
|
+
|
7
|
+
module Vault
|
8
|
+
module Defaults
|
9
|
+
# The default vault address.
|
10
|
+
# @return [String]
|
11
|
+
VAULT_ADDRESS = "https://127.0.0.1:8200".freeze
|
12
|
+
|
13
|
+
# The default path to the vault token on disk.
|
14
|
+
# @return [String]
|
15
|
+
DEFAULT_VAULT_DISK_TOKEN = Pathname.new("#{ENV["HOME"]}/.vault-token").expand_path.freeze
|
16
|
+
|
17
|
+
# The list of SSL ciphers to allow. You should not change this value unless
|
18
|
+
# you absolutely know what you are doing!
|
19
|
+
# @return [String]
|
20
|
+
SSL_CIPHERS = "TLSv1.2:!aNULL:!eNULL".freeze
|
21
|
+
|
22
|
+
# The default number of attempts.
|
23
|
+
# @return [Fixnum]
|
24
|
+
RETRY_ATTEMPTS = 2
|
25
|
+
|
26
|
+
# The default backoff interval.
|
27
|
+
# @return [Fixnum]
|
28
|
+
RETRY_BASE = 0.05
|
29
|
+
|
30
|
+
# The maximum amount of time for a single exponential backoff to sleep.
|
31
|
+
RETRY_MAX_WAIT = 2.0
|
32
|
+
|
33
|
+
# The default size of the connection pool
|
34
|
+
DEFAULT_POOL_SIZE = 16
|
35
|
+
|
36
|
+
# The default timeout in seconds for retrieving a connection from the connection pool
|
37
|
+
DEFAULT_POOL_TIMEOUT = 0.5
|
38
|
+
|
39
|
+
# The set of exceptions that are detect and retried by default
|
40
|
+
# with `with_retries`
|
41
|
+
RETRIED_EXCEPTIONS = [HTTPServerError, MissingRequiredStateError]
|
42
|
+
|
43
|
+
class << self
|
44
|
+
# The list of calculated options for this configurable.
|
45
|
+
# @return [Hash]
|
46
|
+
def options
|
47
|
+
Hash[*Configurable.keys.map { |key| [key, public_send(key)] }.flatten]
|
48
|
+
end
|
49
|
+
|
50
|
+
# The address to communicate with Vault.
|
51
|
+
# @return [String]
|
52
|
+
def address
|
53
|
+
ENV["VAULT_ADDR"] || VAULT_ADDRESS
|
54
|
+
end
|
55
|
+
|
56
|
+
# The vault token to use for authentiation.
|
57
|
+
# @return [String, nil]
|
58
|
+
def token
|
59
|
+
ENV["VAULT_TOKEN"] || fetch_from_disk("VAULT_TOKEN_FILE")
|
60
|
+
end
|
61
|
+
|
62
|
+
def fetch_from_disk(env_var)
|
63
|
+
path = ENV[env_var] ? Pathname.new(ENV[env_var]) : DEFAULT_VAULT_DISK_TOKEN
|
64
|
+
if path.exist? && path.readable?
|
65
|
+
path.read.chomp
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Vault Namespace, if any.
|
70
|
+
# @return [String, nil]
|
71
|
+
def namespace
|
72
|
+
ENV["VAULT_NAMESPACE"]
|
73
|
+
end
|
74
|
+
|
75
|
+
# The SNI host to use when connecting to Vault via TLS.
|
76
|
+
# @return [String, nil]
|
77
|
+
def hostname
|
78
|
+
ENV["VAULT_TLS_SERVER_NAME"]
|
79
|
+
end
|
80
|
+
|
81
|
+
# The number of seconds to wait when trying to open a connection before
|
82
|
+
# timing out
|
83
|
+
# @return [String, nil]
|
84
|
+
def open_timeout
|
85
|
+
ENV["VAULT_OPEN_TIMEOUT"]
|
86
|
+
end
|
87
|
+
|
88
|
+
# The size of the connection pool to communicate with Vault
|
89
|
+
# @return Integer
|
90
|
+
def pool_size
|
91
|
+
if var = ENV["VAULT_POOL_SIZE"]
|
92
|
+
var.to_i
|
93
|
+
else
|
94
|
+
DEFAULT_POOL_SIZE
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# The timeout for getting a connection from the connection pool that communicates with Vault
|
99
|
+
# @return Float
|
100
|
+
def pool_timeout
|
101
|
+
if var = ENV["VAULT_POOL_TIMEOUT"]
|
102
|
+
var.to_f
|
103
|
+
else
|
104
|
+
DEFAULT_POOL_TIMEOUT
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# The HTTP Proxy server address as a string
|
109
|
+
# @return [String, nil]
|
110
|
+
def proxy_address
|
111
|
+
ENV["VAULT_PROXY_ADDRESS"]
|
112
|
+
end
|
113
|
+
|
114
|
+
# The HTTP Proxy server username as a string
|
115
|
+
# @return [String, nil]
|
116
|
+
def proxy_username
|
117
|
+
ENV["VAULT_PROXY_USERNAME"]
|
118
|
+
end
|
119
|
+
|
120
|
+
# The HTTP Proxy user password as a string
|
121
|
+
# @return [String, nil]
|
122
|
+
def proxy_password
|
123
|
+
ENV["VAULT_PROXY_PASSWORD"]
|
124
|
+
end
|
125
|
+
|
126
|
+
# The HTTP Proxy server port as a string
|
127
|
+
# @return [String, nil]
|
128
|
+
def proxy_port
|
129
|
+
ENV["VAULT_PROXY_PORT"]
|
130
|
+
end
|
131
|
+
|
132
|
+
# The number of seconds to wait when reading a response before timing out
|
133
|
+
# @return [String, nil]
|
134
|
+
def read_timeout
|
135
|
+
ENV["VAULT_READ_TIMEOUT"]
|
136
|
+
end
|
137
|
+
|
138
|
+
# The ciphers that will be used when communicating with vault over ssl
|
139
|
+
# You should only change the defaults if the ciphers are not available on
|
140
|
+
# your platform and you know what you are doing
|
141
|
+
# @return [String]
|
142
|
+
def ssl_ciphers
|
143
|
+
ENV["VAULT_SSL_CIPHERS"] || SSL_CIPHERS
|
144
|
+
end
|
145
|
+
|
146
|
+
# The raw contents (as a string) for the pem file. To specify the path to
|
147
|
+
# the pem file, use {#ssl_pem_file} instead. This value is preferred over
|
148
|
+
# the value for {#ssl_pem_file}, if set.
|
149
|
+
# @return [String, nil]
|
150
|
+
def ssl_pem_contents
|
151
|
+
if ENV["VAULT_SSL_PEM_CONTENTS_BASE64"]
|
152
|
+
Base64.decode64(ENV["VAULT_SSL_PEM_CONTENTS_BASE64"])
|
153
|
+
else
|
154
|
+
ENV["VAULT_SSL_PEM_CONTENTS"]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# The path to a pem on disk to use with custom SSL verification
|
159
|
+
# @return [String, nil]
|
160
|
+
def ssl_pem_file
|
161
|
+
ENV["VAULT_SSL_CERT"] || ENV["VAULT_SSL_PEM_FILE"]
|
162
|
+
end
|
163
|
+
|
164
|
+
# Passphrase to the pem file on disk to use with custom SSL verification
|
165
|
+
# @return [String, nil]
|
166
|
+
def ssl_pem_passphrase
|
167
|
+
ENV["VAULT_SSL_CERT_PASSPHRASE"]
|
168
|
+
end
|
169
|
+
|
170
|
+
# The path to the CA cert on disk to use for certificate verification
|
171
|
+
# @return [String, nil]
|
172
|
+
def ssl_ca_cert
|
173
|
+
ENV["VAULT_CACERT"]
|
174
|
+
end
|
175
|
+
|
176
|
+
# The CA cert store to use for certificate verification
|
177
|
+
# @return [OpenSSL::X509::Store, nil]
|
178
|
+
def ssl_cert_store
|
179
|
+
nil
|
180
|
+
end
|
181
|
+
|
182
|
+
# The path to the directory on disk holding CA certs to use
|
183
|
+
# for certificate verification
|
184
|
+
# @return [String, nil]
|
185
|
+
def ssl_ca_path
|
186
|
+
ENV["VAULT_CAPATH"]
|
187
|
+
end
|
188
|
+
|
189
|
+
# Verify SSL requests (default: true)
|
190
|
+
# @return [true, false]
|
191
|
+
def ssl_verify
|
192
|
+
# Vault CLI uses this envvar, so accept it by precedence
|
193
|
+
if !ENV["VAULT_SKIP_VERIFY"].nil?
|
194
|
+
return false
|
195
|
+
end
|
196
|
+
|
197
|
+
if ENV["VAULT_SSL_VERIFY"].nil?
|
198
|
+
true
|
199
|
+
else
|
200
|
+
%w[t y].include?(ENV["VAULT_SSL_VERIFY"].downcase[0])
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# The number of seconds to wait for connecting and verifying SSL
|
205
|
+
# @return [String, nil]
|
206
|
+
def ssl_timeout
|
207
|
+
ENV["VAULT_SSL_TIMEOUT"]
|
208
|
+
end
|
209
|
+
|
210
|
+
# A default meta-attribute to set all timeout values - individually set
|
211
|
+
# timeout values will take precedence
|
212
|
+
# @return [String, nil]
|
213
|
+
def timeout
|
214
|
+
ENV["VAULT_TIMEOUT"]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
data/lib/vault/encode.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Copyright (c) HashiCorp, Inc.
|
2
|
+
# SPDX-License-Identifier: MPL-2.0
|
3
|
+
|
4
|
+
module Vault
|
5
|
+
module EncodePath
|
6
|
+
|
7
|
+
# Encodes a string according to the rules for URL paths. This is
|
8
|
+
# used as opposed to CGI.escape because in a URL path, space
|
9
|
+
# needs to be escaped as %20 and CGI.escapes a space as +.
|
10
|
+
#
|
11
|
+
# @param [String]
|
12
|
+
#
|
13
|
+
# @return [String]
|
14
|
+
def encode_path(path)
|
15
|
+
path.b.gsub(%r!([^a-zA-Z0-9_.-/]+)!) { |m|
|
16
|
+
'%' + m.unpack('H2' * m.bytesize).join('%').upcase
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
module_function :encode_path
|
21
|
+
end
|
22
|
+
end
|
data/lib/vault/errors.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# Copyright (c) HashiCorp, Inc.
|
2
|
+
# SPDX-License-Identifier: MPL-2.0
|
3
|
+
|
4
|
+
module Vault
|
5
|
+
class VaultError < RuntimeError; end
|
6
|
+
|
7
|
+
class MissingTokenError < VaultError
|
8
|
+
def initialize
|
9
|
+
super <<-EOH
|
10
|
+
Missing Vault token! I cannot make requests to Vault without a token. Please
|
11
|
+
set a Vault token in the client:
|
12
|
+
|
13
|
+
Vault.token = "42d1dee5-eb6e-102c-8d23-cc3ba875da51"
|
14
|
+
|
15
|
+
or authenticate with Vault using the Vault CLI:
|
16
|
+
|
17
|
+
$ vault auth ...
|
18
|
+
|
19
|
+
or set the environment variable $VAULT_TOKEN to the token value:
|
20
|
+
|
21
|
+
$ export VAULT_TOKEN="..."
|
22
|
+
|
23
|
+
Please refer to the documentation for more examples.
|
24
|
+
EOH
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class MissingRequiredStateError < VaultError
|
29
|
+
def initialize
|
30
|
+
super <<-EOH
|
31
|
+
The performance standby node does not yet have the
|
32
|
+
most recent index state required to authenticate
|
33
|
+
the request.
|
34
|
+
|
35
|
+
Generally, the request should be retried with the with_retries clause.
|
36
|
+
EOH
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class HTTPConnectionError < VaultError
|
41
|
+
attr_reader :address
|
42
|
+
|
43
|
+
def initialize(address, exception)
|
44
|
+
@address = address
|
45
|
+
@exception = exception
|
46
|
+
|
47
|
+
super <<-EOH
|
48
|
+
The Vault server at `#{address}' is not currently
|
49
|
+
accepting connections. Please ensure that the server is running and that your
|
50
|
+
authentication information is correct.
|
51
|
+
|
52
|
+
The original error was `#{exception.class}'. Additional information (if any) is
|
53
|
+
shown below:
|
54
|
+
|
55
|
+
#{exception.message}
|
56
|
+
|
57
|
+
Please refer to the documentation for more help.
|
58
|
+
EOH
|
59
|
+
end
|
60
|
+
|
61
|
+
def original_exception
|
62
|
+
@exception
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class HTTPError < VaultError
|
67
|
+
attr_reader :address, :response, :code, :errors
|
68
|
+
|
69
|
+
def initialize(address, response, errors = [])
|
70
|
+
@address, @response, @errors = address, response, errors
|
71
|
+
@code = response.code.to_i
|
72
|
+
errors = errors.map { |error| " * #{error}" }
|
73
|
+
|
74
|
+
super <<-EOH
|
75
|
+
The Vault server at `#{address}' responded with a #{code}.
|
76
|
+
Any additional information the server supplied is shown below:
|
77
|
+
|
78
|
+
#{errors.join("\n").rstrip}
|
79
|
+
|
80
|
+
Please refer to the documentation for help.
|
81
|
+
EOH
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class HTTPClientError < HTTPError; end
|
86
|
+
class HTTPServerError < HTTPError; end
|
87
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Copyright (c) HashiCorp, Inc.
|
2
|
+
# SPDX-License-Identifier: MPL-2.0
|
3
|
+
|
4
|
+
##
|
5
|
+
# A Net::HTTP connection wrapper that holds extra information for managing the
|
6
|
+
# connection's lifetime.
|
7
|
+
|
8
|
+
module Vault
|
9
|
+
class PersistentHTTP::Connection # :nodoc:
|
10
|
+
|
11
|
+
attr_accessor :http
|
12
|
+
|
13
|
+
attr_accessor :last_use
|
14
|
+
|
15
|
+
attr_accessor :requests
|
16
|
+
|
17
|
+
attr_accessor :ssl_generation
|
18
|
+
|
19
|
+
def initialize http_class, http_args, ssl_generation
|
20
|
+
@http = http_class.new(*http_args)
|
21
|
+
@ssl_generation = ssl_generation
|
22
|
+
|
23
|
+
reset
|
24
|
+
end
|
25
|
+
|
26
|
+
def finish
|
27
|
+
@http.finish
|
28
|
+
rescue IOError
|
29
|
+
ensure
|
30
|
+
reset
|
31
|
+
end
|
32
|
+
|
33
|
+
def reset
|
34
|
+
@last_use = PersistentHTTP::EPOCH
|
35
|
+
@requests = 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def ressl ssl_generation
|
39
|
+
@ssl_generation = ssl_generation
|
40
|
+
|
41
|
+
finish
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Copyright (c) HashiCorp, Inc.
|
2
|
+
# SPDX-License-Identifier: MPL-2.0
|
3
|
+
|
4
|
+
module Vault
|
5
|
+
class PersistentHTTP::Pool < Vault::ConnectionPool # :nodoc:
|
6
|
+
|
7
|
+
attr_reader :available # :nodoc:
|
8
|
+
attr_reader :key # :nodoc:
|
9
|
+
|
10
|
+
def initialize(options = {}, &block)
|
11
|
+
super
|
12
|
+
|
13
|
+
@available = PersistentHTTP::TimedStackMulti.new(@size, &block)
|
14
|
+
@key = :"current-#{@available.object_id}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def checkin net_http_args
|
18
|
+
stack = Thread.current[@key][net_http_args]
|
19
|
+
|
20
|
+
raise ConnectionPool::Error, 'no connections are checked out' if
|
21
|
+
stack.empty?
|
22
|
+
|
23
|
+
conn = stack.pop
|
24
|
+
|
25
|
+
if stack.empty?
|
26
|
+
@available.push conn, connection_args: net_http_args
|
27
|
+
end
|
28
|
+
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def checkout net_http_args
|
33
|
+
stacks = Thread.current[@key] ||= Hash.new { |h, k| h[k] = [] }
|
34
|
+
stack = stacks[net_http_args]
|
35
|
+
|
36
|
+
if stack.empty? then
|
37
|
+
conn = @available.pop @timeout, connection_args: net_http_args
|
38
|
+
else
|
39
|
+
conn = stack.last
|
40
|
+
end
|
41
|
+
|
42
|
+
stack.push conn
|
43
|
+
|
44
|
+
conn
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
require_relative 'timed_stack_multi'
|
51
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# Copyright (c) HashiCorp, Inc.
|
2
|
+
# SPDX-License-Identifier: MPL-2.0
|
3
|
+
|
4
|
+
module Vault
|
5
|
+
class PersistentHTTP::TimedStackMulti < ConnectionPool::TimedStack # :nodoc:
|
6
|
+
|
7
|
+
def initialize(size = 0, &block)
|
8
|
+
super
|
9
|
+
|
10
|
+
@enqueued = 0
|
11
|
+
@ques = Hash.new { |h, k| h[k] = [] }
|
12
|
+
@lru = {}
|
13
|
+
@key = :"connection_args-#{object_id}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def empty?
|
17
|
+
(@created - @enqueued) >= @max
|
18
|
+
end
|
19
|
+
|
20
|
+
def length
|
21
|
+
@max - @created + @enqueued
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def connection_stored? options = {} # :nodoc:
|
27
|
+
!@ques[options[:connection_args]].empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch_connection options = {} # :nodoc:
|
31
|
+
connection_args = options[:connection_args]
|
32
|
+
|
33
|
+
@enqueued -= 1
|
34
|
+
lru_update connection_args
|
35
|
+
@ques[connection_args].pop
|
36
|
+
end
|
37
|
+
|
38
|
+
def lru_update connection_args # :nodoc:
|
39
|
+
@lru.delete connection_args
|
40
|
+
@lru[connection_args] = true
|
41
|
+
end
|
42
|
+
|
43
|
+
def shutdown_connections # :nodoc:
|
44
|
+
@ques.each_key do |key|
|
45
|
+
super connection_args: key
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def store_connection obj, options = {} # :nodoc:
|
50
|
+
@ques[options[:connection_args]].push obj
|
51
|
+
@enqueued += 1
|
52
|
+
end
|
53
|
+
|
54
|
+
def try_create options = {} # :nodoc:
|
55
|
+
connection_args = options[:connection_args]
|
56
|
+
|
57
|
+
if @created >= @max && @enqueued >= 1
|
58
|
+
oldest, = @lru.first
|
59
|
+
@lru.delete oldest
|
60
|
+
@ques[oldest].pop
|
61
|
+
|
62
|
+
@created -= 1
|
63
|
+
end
|
64
|
+
|
65
|
+
if @created < @max
|
66
|
+
@created += 1
|
67
|
+
lru_update connection_args
|
68
|
+
return @create_block.call(connection_args)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|