vault 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/CHANGELOG.md +17 -1
- data/lib/vault/api.rb +1 -0
- data/lib/vault/api/approle.rb +218 -0
- data/lib/vault/api/auth.rb +26 -2
- data/lib/vault/api/auth_tls.rb +3 -3
- data/lib/vault/api/auth_token.rb +2 -2
- data/lib/vault/api/help.rb +1 -1
- data/lib/vault/api/logical.rb +4 -4
- data/lib/vault/api/sys/audit.rb +2 -2
- data/lib/vault/api/sys/auth.rb +51 -2
- data/lib/vault/api/sys/leader.rb +5 -0
- data/lib/vault/api/sys/mount.rb +3 -3
- data/lib/vault/api/sys/policy.rb +3 -3
- data/lib/vault/client.rb +92 -74
- data/lib/vault/configurable.rb +1 -0
- data/lib/vault/defaults.rb +7 -1
- data/lib/vault/encode.rb +19 -0
- data/lib/vault/request.rb +2 -0
- data/lib/vault/vendor/connection_pool.rb +150 -0
- data/lib/vault/vendor/connection_pool/timed_stack.rb +178 -0
- data/lib/vault/vendor/connection_pool/version.rb +5 -0
- data/lib/vault/vendor/net/http/persistent.rb +1154 -0
- data/lib/vault/vendor/net/http/persistent/connection.rb +42 -0
- data/lib/vault/vendor/net/http/persistent/pool.rb +48 -0
- data/lib/vault/vendor/net/http/persistent/timed_stack_multi.rb +70 -0
- data/lib/vault/version.rb +1 -1
- metadata +12 -3
data/lib/vault/api/sys/leader.rb
CHANGED
data/lib/vault/api/sys/mount.rb
CHANGED
@@ -48,7 +48,7 @@ module Vault
|
|
48
48
|
payload = { type: type }
|
49
49
|
payload[:description] = description if !description.nil?
|
50
50
|
|
51
|
-
client.post("/v1/sys/mounts/#{
|
51
|
+
client.post("/v1/sys/mounts/#{encode_path(path)}", JSON.fast_generate(payload))
|
52
52
|
return true
|
53
53
|
end
|
54
54
|
|
@@ -62,7 +62,7 @@ module Vault
|
|
62
62
|
# @param [Hash] data
|
63
63
|
# the data to write
|
64
64
|
def mount_tune(path, data = {})
|
65
|
-
json = client.post("/v1/sys/mounts/#{
|
65
|
+
json = client.post("/v1/sys/mounts/#{encode_path(path)}/tune", JSON.fast_generate(data))
|
66
66
|
return true
|
67
67
|
end
|
68
68
|
|
@@ -77,7 +77,7 @@ module Vault
|
|
77
77
|
#
|
78
78
|
# @return [true]
|
79
79
|
def unmount(path)
|
80
|
-
client.delete("/v1/sys/mounts/#{
|
80
|
+
client.delete("/v1/sys/mounts/#{encode_path(path)}")
|
81
81
|
return true
|
82
82
|
end
|
83
83
|
|
data/lib/vault/api/sys/policy.rb
CHANGED
@@ -40,7 +40,7 @@ module Vault
|
|
40
40
|
#
|
41
41
|
# @return [Policy, nil]
|
42
42
|
def policy(name)
|
43
|
-
json = client.get("/v1/sys/policy/#{
|
43
|
+
json = client.get("/v1/sys/policy/#{encode_path(name)}")
|
44
44
|
return Policy.decode(json)
|
45
45
|
rescue HTTPError => e
|
46
46
|
return nil if e.code == 404
|
@@ -70,7 +70,7 @@ module Vault
|
|
70
70
|
#
|
71
71
|
# @return [true]
|
72
72
|
def put_policy(name, rules)
|
73
|
-
client.put("/v1/sys/policy/#{
|
73
|
+
client.put("/v1/sys/policy/#{encode_path(name)}", JSON.fast_generate(
|
74
74
|
rules: rules,
|
75
75
|
))
|
76
76
|
return true
|
@@ -85,7 +85,7 @@ module Vault
|
|
85
85
|
# @param [String] name
|
86
86
|
# the name of the policy
|
87
87
|
def delete_policy(name)
|
88
|
-
client.delete("/v1/sys/policy/#{
|
88
|
+
client.delete("/v1/sys/policy/#{encode_path(name)}")
|
89
89
|
return true
|
90
90
|
end
|
91
91
|
end
|
data/lib/vault/client.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
require "cgi"
|
2
2
|
require "json"
|
3
|
-
require "net/http"
|
4
|
-
require "net/https"
|
5
3
|
require "uri"
|
6
4
|
|
5
|
+
require_relative "vendor/net/http/persistent"
|
6
|
+
|
7
7
|
require_relative "configurable"
|
8
8
|
require_relative "errors"
|
9
9
|
require_relative "version"
|
10
|
+
require_relative "encode"
|
10
11
|
|
11
12
|
module Vault
|
12
13
|
class Client
|
@@ -53,8 +54,15 @@ module Vault
|
|
53
54
|
# only add them if they are defiend
|
54
55
|
a << Net::ReadTimeout if defined?(Net::ReadTimeout)
|
55
56
|
a << Net::OpenTimeout if defined?(Net::OpenTimeout)
|
57
|
+
|
58
|
+
a << Net::HTTP::Persistent::Error
|
56
59
|
end.freeze
|
57
60
|
|
61
|
+
# Indicates a requested operation is not possible due to security
|
62
|
+
# concerns.
|
63
|
+
class SecurityError < RuntimeError
|
64
|
+
end
|
65
|
+
|
58
66
|
include Vault::Configurable
|
59
67
|
|
60
68
|
# Create a new Client with the given options. Any options given take
|
@@ -67,6 +75,71 @@ module Vault
|
|
67
75
|
value = options.key?(key) ? options[key] : Defaults.public_send(key)
|
68
76
|
instance_variable_set(:"@#{key}", value)
|
69
77
|
end
|
78
|
+
|
79
|
+
@nhp = Net::HTTP::Persistent.new(name: "vault-ruby")
|
80
|
+
|
81
|
+
if proxy_address
|
82
|
+
proxy_uri = URI.parse "http://#{proxy_address}"
|
83
|
+
|
84
|
+
proxy_uri.port = proxy_port if proxy_port
|
85
|
+
|
86
|
+
if proxy_username
|
87
|
+
proxy_uri.user = proxy_username
|
88
|
+
proxy_uri.password = proxy_password
|
89
|
+
end
|
90
|
+
|
91
|
+
@nhp.proxy = proxy_uri
|
92
|
+
end
|
93
|
+
|
94
|
+
# Use a custom open timeout
|
95
|
+
if open_timeout || timeout
|
96
|
+
@nhp.open_timeout = (open_timeout || timeout).to_i
|
97
|
+
end
|
98
|
+
|
99
|
+
# Use a custom read timeout
|
100
|
+
if read_timeout || timeout
|
101
|
+
@nhp.read_timeout = (read_timeout || timeout).to_i
|
102
|
+
end
|
103
|
+
|
104
|
+
@nhp.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
105
|
+
|
106
|
+
# Vault requires TLS1.2
|
107
|
+
@nhp.ssl_version = "TLSv1_2"
|
108
|
+
|
109
|
+
# Only use secure ciphers
|
110
|
+
@nhp.ciphers = ssl_ciphers
|
111
|
+
|
112
|
+
# Custom pem files, no problem!
|
113
|
+
pem = ssl_pem_contents || (ssl_pem_file ? File.read(ssl_pem_file) : nil)
|
114
|
+
if pem
|
115
|
+
@nhp.cert = OpenSSL::X509::Certificate.new(pem)
|
116
|
+
@nhp.key = OpenSSL::PKey::RSA.new(pem, ssl_pem_passphrase)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Use custom CA cert for verification
|
120
|
+
if ssl_ca_cert
|
121
|
+
@nhp.ca_file = ssl_ca_cert
|
122
|
+
end
|
123
|
+
|
124
|
+
# Use custom CA path that contains CA certs
|
125
|
+
if ssl_ca_path
|
126
|
+
@nhp.ca_path = ssl_ca_path
|
127
|
+
end
|
128
|
+
|
129
|
+
if ssl_cert_store
|
130
|
+
@nhp.cert_store = ssl_cert_store
|
131
|
+
end
|
132
|
+
|
133
|
+
# Naughty, naughty, naughty! Don't blame me when someone hops in
|
134
|
+
# and executes a MITM attack!
|
135
|
+
if !ssl_verify
|
136
|
+
@nhp.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
137
|
+
end
|
138
|
+
|
139
|
+
# Use custom timeout for connecting and verifying via SSL
|
140
|
+
if ssl_timeout || timeout
|
141
|
+
@nhp.ssl_timeout = (ssl_timeout || timeout).to_i
|
142
|
+
end
|
70
143
|
end
|
71
144
|
|
72
145
|
# Creates and yields a new client object with the given token. This may be
|
@@ -148,6 +221,10 @@ module Vault
|
|
148
221
|
uri = build_uri(verb, path, data)
|
149
222
|
request = class_for_request(verb).new(uri.request_uri)
|
150
223
|
|
224
|
+
if proxy_address and uri.scheme.downcase == "https"
|
225
|
+
raise SecurityError, "no direct https connection to vault"
|
226
|
+
end
|
227
|
+
|
151
228
|
# Get a list of headers
|
152
229
|
headers = DEFAULT_HEADERS.merge(headers)
|
153
230
|
|
@@ -174,82 +251,23 @@ module Vault
|
|
174
251
|
end
|
175
252
|
end
|
176
253
|
|
177
|
-
# Create the HTTP connection object - since the proxy information defaults
|
178
|
-
# to +nil+, we can just pass it to the initializer method instead of doing
|
179
|
-
# crazy strange conditionals.
|
180
|
-
connection = Net::HTTP.new(uri.host, uri.port,
|
181
|
-
proxy_address, proxy_port, proxy_username, proxy_password)
|
182
|
-
|
183
|
-
# Use a custom open timeout
|
184
|
-
if open_timeout || timeout
|
185
|
-
connection.open_timeout = (open_timeout || timeout).to_i
|
186
|
-
end
|
187
|
-
|
188
|
-
# Use a custom read timeout
|
189
|
-
if read_timeout || timeout
|
190
|
-
connection.read_timeout = (read_timeout || timeout).to_i
|
191
|
-
end
|
192
|
-
|
193
|
-
# Apply SSL, if applicable
|
194
|
-
if uri.scheme == "https"
|
195
|
-
# Turn on SSL
|
196
|
-
connection.use_ssl = true
|
197
|
-
|
198
|
-
# Vault requires TLS1.2
|
199
|
-
connection.ssl_version = "TLSv1_2"
|
200
|
-
|
201
|
-
# Only use secure ciphers
|
202
|
-
connection.ciphers = ssl_ciphers
|
203
|
-
|
204
|
-
# Custom pem files, no problem!
|
205
|
-
pem = ssl_pem_contents || ssl_pem_file ? File.read(ssl_pem_file) : nil
|
206
|
-
if pem
|
207
|
-
connection.cert = OpenSSL::X509::Certificate.new(pem)
|
208
|
-
connection.key = OpenSSL::PKey::RSA.new(pem, ssl_pem_passphrase)
|
209
|
-
connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
210
|
-
end
|
211
|
-
|
212
|
-
# Use custom CA cert for verification
|
213
|
-
if ssl_ca_cert
|
214
|
-
connection.ca_file = ssl_ca_cert
|
215
|
-
end
|
216
|
-
|
217
|
-
# Use custom CA path that contains CA certs
|
218
|
-
if ssl_ca_path
|
219
|
-
connection.ca_path = ssl_ca_path
|
220
|
-
end
|
221
|
-
|
222
|
-
# Naughty, naughty, naughty! Don't blame me when someone hops in
|
223
|
-
# and executes a MITM attack!
|
224
|
-
if !ssl_verify
|
225
|
-
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
226
|
-
end
|
227
|
-
|
228
|
-
# Use custom timeout for connecting and verifying via SSL
|
229
|
-
if ssl_timeout || timeout
|
230
|
-
connection.ssl_timeout = (ssl_timeout || timeout).to_i
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
254
|
begin
|
235
255
|
# Create a connection using the block form, which will ensure the socket
|
236
256
|
# is properly closed in the event of an error.
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
data = {}
|
246
|
-
end
|
247
|
-
request(verb, response[LOCATION_HEADER], data, headers)
|
248
|
-
when Net::HTTPSuccess
|
249
|
-
success(response)
|
250
|
-
else
|
251
|
-
error(response)
|
257
|
+
response = @nhp.request(uri, request)
|
258
|
+
|
259
|
+
case response
|
260
|
+
when Net::HTTPRedirection
|
261
|
+
# On a redirect of a GET or HEAD request, the URL already contains
|
262
|
+
# the data as query string parameters.
|
263
|
+
if [:head, :get].include?(verb)
|
264
|
+
data = {}
|
252
265
|
end
|
266
|
+
request(verb, response[LOCATION_HEADER], data, headers)
|
267
|
+
when Net::HTTPSuccess
|
268
|
+
success(response)
|
269
|
+
else
|
270
|
+
error(response)
|
253
271
|
end
|
254
272
|
rescue *RESCUED_EXCEPTIONS => e
|
255
273
|
raise HTTPConnectionError.new(address, e)
|
data/lib/vault/configurable.rb
CHANGED
data/lib/vault/defaults.rb
CHANGED
@@ -123,7 +123,13 @@ module Vault
|
|
123
123
|
def ssl_ca_cert
|
124
124
|
ENV["VAULT_CACERT"]
|
125
125
|
end
|
126
|
-
|
126
|
+
|
127
|
+
# The CA cert store to use for certificate verification
|
128
|
+
# @return [OpenSSL::X509::Store, nil]
|
129
|
+
def ssl_cert_store
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
127
133
|
# The path to the directory on disk holding CA certs to use
|
128
134
|
# for certificate verification
|
129
135
|
# @return [String, nil]
|
data/lib/vault/encode.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Vault
|
2
|
+
module EncodePath
|
3
|
+
|
4
|
+
# Encodes a string according to the rules for URL paths. This is
|
5
|
+
# used as opposed to CGI.escape because in a URL path, space
|
6
|
+
# needs to be escaped as %20 and CGI.escapes a space as +.
|
7
|
+
#
|
8
|
+
# @param [String]
|
9
|
+
#
|
10
|
+
# @return [String]
|
11
|
+
def encode_path(path)
|
12
|
+
path.b.gsub(%r!([^a-zA-Z0-9_.-/]+)!) { |m|
|
13
|
+
'%' + m.unpack('H2' * m.bytesize).join('%').upcase
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
module_function :encode_path
|
18
|
+
end
|
19
|
+
end
|
data/lib/vault/request.rb
CHANGED
@@ -0,0 +1,150 @@
|
|
1
|
+
require_relative 'connection_pool/version'
|
2
|
+
require_relative 'connection_pool/timed_stack'
|
3
|
+
|
4
|
+
|
5
|
+
# Generic connection pool class for e.g. sharing a limited number of network connections
|
6
|
+
# among many threads. Note: Connections are lazily created.
|
7
|
+
#
|
8
|
+
# Example usage with block (faster):
|
9
|
+
#
|
10
|
+
# @pool = ConnectionPool.new { Redis.new }
|
11
|
+
#
|
12
|
+
# @pool.with do |redis|
|
13
|
+
# redis.lpop('my-list') if redis.llen('my-list') > 0
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# Using optional timeout override (for that single invocation)
|
17
|
+
#
|
18
|
+
# @pool.with(:timeout => 2.0) do |redis|
|
19
|
+
# redis.lpop('my-list') if redis.llen('my-list') > 0
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Example usage replacing an existing connection (slower):
|
23
|
+
#
|
24
|
+
# $redis = ConnectionPool.wrap { Redis.new }
|
25
|
+
#
|
26
|
+
# def do_work
|
27
|
+
# $redis.lpop('my-list') if $redis.llen('my-list') > 0
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# Accepts the following options:
|
31
|
+
# - :size - number of connections to pool, defaults to 5
|
32
|
+
# - :timeout - amount of time to wait for a connection if none currently available, defaults to 5 seconds
|
33
|
+
#
|
34
|
+
module Vault
|
35
|
+
class ConnectionPool
|
36
|
+
DEFAULTS = {size: 5, timeout: 5}
|
37
|
+
|
38
|
+
class Error < RuntimeError
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.wrap(options, &block)
|
42
|
+
Wrapper.new(options, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(options = {}, &block)
|
46
|
+
raise ArgumentError, 'Connection pool requires a block' unless block
|
47
|
+
|
48
|
+
options = DEFAULTS.merge(options)
|
49
|
+
|
50
|
+
@size = options.fetch(:size)
|
51
|
+
@timeout = options.fetch(:timeout)
|
52
|
+
|
53
|
+
@available = TimedStack.new(@size, &block)
|
54
|
+
@key = :"current-#{@available.object_id}"
|
55
|
+
end
|
56
|
+
|
57
|
+
if Thread.respond_to?(:handle_interrupt)
|
58
|
+
|
59
|
+
# MRI
|
60
|
+
def with(options = {})
|
61
|
+
Thread.handle_interrupt(Exception => :never) do
|
62
|
+
conn = checkout(options)
|
63
|
+
begin
|
64
|
+
Thread.handle_interrupt(Exception => :immediate) do
|
65
|
+
yield conn
|
66
|
+
end
|
67
|
+
ensure
|
68
|
+
checkin
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
else
|
74
|
+
|
75
|
+
# jruby 1.7.x
|
76
|
+
def with(options = {})
|
77
|
+
conn = checkout(options)
|
78
|
+
begin
|
79
|
+
yield conn
|
80
|
+
ensure
|
81
|
+
checkin
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
def checkout(options = {})
|
88
|
+
conn = if stack.empty?
|
89
|
+
timeout = options[:timeout] || @timeout
|
90
|
+
@available.pop(timeout: timeout)
|
91
|
+
else
|
92
|
+
stack.last
|
93
|
+
end
|
94
|
+
|
95
|
+
stack.push conn
|
96
|
+
conn
|
97
|
+
end
|
98
|
+
|
99
|
+
def checkin
|
100
|
+
conn = pop_connection # mutates stack, must be on its own line
|
101
|
+
@available.push(conn) if stack.empty?
|
102
|
+
|
103
|
+
nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def shutdown(&block)
|
107
|
+
@available.shutdown(&block)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def pop_connection
|
113
|
+
if stack.empty?
|
114
|
+
raise ConnectionPool::Error, 'no connections are checked out'
|
115
|
+
else
|
116
|
+
stack.pop
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def stack
|
121
|
+
::Thread.current[@key] ||= []
|
122
|
+
end
|
123
|
+
|
124
|
+
class Wrapper < ::BasicObject
|
125
|
+
METHODS = [:with, :pool_shutdown]
|
126
|
+
|
127
|
+
def initialize(options = {}, &block)
|
128
|
+
@pool = ::ConnectionPool.new(options, &block)
|
129
|
+
end
|
130
|
+
|
131
|
+
def with(&block)
|
132
|
+
@pool.with(&block)
|
133
|
+
end
|
134
|
+
|
135
|
+
def pool_shutdown(&block)
|
136
|
+
@pool.shutdown(&block)
|
137
|
+
end
|
138
|
+
|
139
|
+
def respond_to?(id, *args)
|
140
|
+
METHODS.include?(id) || with { |c| c.respond_to?(id, *args) }
|
141
|
+
end
|
142
|
+
|
143
|
+
def method_missing(name, *args, &block)
|
144
|
+
with do |connection|
|
145
|
+
connection.send(name, *args, &block)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|