vault 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|