vault-kv 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +42 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +29 -0
  5. data/CHANGELOG.md +228 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +362 -0
  8. data/README.md +212 -0
  9. data/Rakefile +6 -0
  10. data/lib/vault.rb +49 -0
  11. data/lib/vault/api.rb +13 -0
  12. data/lib/vault/api/approle.rb +218 -0
  13. data/lib/vault/api/auth.rb +316 -0
  14. data/lib/vault/api/auth_tls.rb +92 -0
  15. data/lib/vault/api/auth_token.rb +242 -0
  16. data/lib/vault/api/help.rb +33 -0
  17. data/lib/vault/api/kv.rb +207 -0
  18. data/lib/vault/api/logical.rb +150 -0
  19. data/lib/vault/api/secret.rb +168 -0
  20. data/lib/vault/api/sys.rb +25 -0
  21. data/lib/vault/api/sys/audit.rb +91 -0
  22. data/lib/vault/api/sys/auth.rb +116 -0
  23. data/lib/vault/api/sys/health.rb +63 -0
  24. data/lib/vault/api/sys/init.rb +83 -0
  25. data/lib/vault/api/sys/leader.rb +48 -0
  26. data/lib/vault/api/sys/lease.rb +49 -0
  27. data/lib/vault/api/sys/mount.rb +103 -0
  28. data/lib/vault/api/sys/policy.rb +92 -0
  29. data/lib/vault/api/sys/seal.rb +81 -0
  30. data/lib/vault/client.rb +447 -0
  31. data/lib/vault/configurable.rb +48 -0
  32. data/lib/vault/defaults.rb +197 -0
  33. data/lib/vault/encode.rb +19 -0
  34. data/lib/vault/errors.rb +72 -0
  35. data/lib/vault/persistent.rb +1158 -0
  36. data/lib/vault/persistent/connection.rb +42 -0
  37. data/lib/vault/persistent/pool.rb +48 -0
  38. data/lib/vault/persistent/timed_stack_multi.rb +70 -0
  39. data/lib/vault/request.rb +43 -0
  40. data/lib/vault/response.rb +89 -0
  41. data/lib/vault/vendor/connection_pool.rb +150 -0
  42. data/lib/vault/vendor/connection_pool/timed_stack.rb +178 -0
  43. data/lib/vault/vendor/connection_pool/version.rb +5 -0
  44. data/lib/vault/version.rb +3 -0
  45. data/vault.gemspec +30 -0
  46. metadata +186 -0
data/README.md ADDED
@@ -0,0 +1,212 @@
1
+ Vault Ruby Client [![Build Status](https://secure.travis-ci.org/hashicorp/vault-ruby.svg)](http://travis-ci.org/hashicorp/vault-ruby)
2
+ =================
3
+
4
+ Vault is the official Ruby client for interacting with [Vault](https://vaultproject.io) by HashiCorp.
5
+
6
+ **The documentation in this README corresponds to the master branch of the Vault Ruby client. It may contain unreleased features or different APIs than the most recently released version. Please see the Git tag that corresponds to your version of the Vault Ruby client for the proper documentation.**
7
+
8
+ Quick Start
9
+ -----------
10
+ Install Ruby 2.0+: [Guide](https://www.ruby-lang.org/en/documentation/installation/).
11
+
12
+ > Please note that Vault Ruby may work on older Ruby installations like Ruby 1.9, but you **should not** use these versions of Ruby when communicating with a Vault server. Ruby 1.9 has [reached EOL](https://www.ruby-lang.org/en/news/2014/01/10/ruby-1-9-3-will-end-on-2015/) and will no longer receive important security patches or maintenance updates. There _are known security vulnerabilities_ specifically around SSL ciphers, which this library uses to communicate with a Vault server. While many distros still ship with Ruby 1.9 as the default, you are **highly discouraged** from using this library on any version of Ruby lower than Ruby 2.0.
13
+
14
+ Install via Rubygems:
15
+
16
+ $ gem install vault
17
+
18
+ or add it to your Gemfile if you're using Bundler:
19
+
20
+ ```ruby
21
+ gem "vault", "~> 0.1"
22
+ ```
23
+
24
+ and then run the `bundle` command to install.
25
+
26
+ Start a Vault client:
27
+
28
+ ```ruby
29
+ Vault.address = "http://127.0.0.1:8200" # Also reads from ENV["VAULT_ADDR"]
30
+ Vault.token = "abcd-1234" # Also reads from ENV["VAULT_TOKEN"]
31
+
32
+ Vault.sys.mounts #=> { :secret => #<struct Vault::Mount type="generic", description="generic secret storage"> }
33
+ ```
34
+
35
+ Usage
36
+ -----
37
+ The following configuration options are available:
38
+
39
+ ```ruby
40
+ Vault.configure do |config|
41
+ # The address of the Vault server, also read as ENV["VAULT_ADDR"]
42
+ config.address = "https://127.0.0.1:8200"
43
+
44
+ # The token to authenticate with Vault, also read as ENV["VAULT_TOKEN"]
45
+ config.token = "abcd-1234"
46
+
47
+ # Proxy connection information, also read as ENV["VAULT_PROXY_(thing)"]
48
+ config.proxy_address = "..."
49
+ config.proxy_port = "..."
50
+ config.proxy_username = "..."
51
+ config.proxy_password = "..."
52
+
53
+ # Custom SSL PEM, also read as ENV["VAULT_SSL_CERT"]
54
+ config.ssl_pem_file = "/path/on/disk.pem"
55
+
56
+ # As an alternative to a pem file, you can provide the raw PEM string, also read in the following order of preference:
57
+ # ENV["VAULT_SSL_PEM_CONTENTS_BASE64"] then ENV["VAULT_SSL_PEM_CONTENTS"]
58
+ config.ssl_pem_contents = "-----BEGIN ENCRYPTED..."
59
+
60
+ # Use SSL verification, also read as ENV["VAULT_SSL_VERIFY"]
61
+ config.ssl_verify = false
62
+
63
+ # Timeout the connection after a certain amount of time (seconds), also read
64
+ # as ENV["VAULT_TIMEOUT"]
65
+ config.timeout = 30
66
+
67
+ # It is also possible to have finer-grained controls over the timeouts, these
68
+ # may also be read as environment variables
69
+ config.ssl_timeout = 5
70
+ config.open_timeout = 5
71
+ config.read_timeout = 30
72
+ end
73
+ ```
74
+
75
+ If you do not want the Vault singleton, or if you need to communicate with multiple Vault servers at once, you can create independent client objects:
76
+
77
+ ```ruby
78
+ client_1 = Vault::Client.new(address: "https://vault.mycompany.com")
79
+ client_2 = Vault::Client.new(address: "https://other-vault.mycompany.com")
80
+ ```
81
+
82
+ And if you want to authenticate with a `AWS EC2` :
83
+
84
+ ```ruby
85
+ # Export VAULT_ADDR to ENV then
86
+ # Get the pkcs7 value from AWS
87
+ signature = `curl http://169.254.169.254/latest/dynamic/instance-identity/pkcs7`
88
+ vault_token = Vault.auth.aws_ec2(ENV['EC2_ROLE'], signature, nil)
89
+ vault_client = Vault::Client.new(address: ENV["VAULT_ADDR"], token: vault_token.auth.client_token)
90
+ ```
91
+
92
+ ### Making requests
93
+ All of the methods and API calls are heavily documented with examples inline using YARD. In order to keep the examples versioned with the code, the README only lists a few examples for using the Vault gem. Please see the inline documentation for the full API documentation. The tests in the 'spec' directory are an additional source of examples.
94
+
95
+ Idempotent requests can be wrapped with a `with_retries` clause to automatically retry on certain connection errors. For example, to retry on socket/network-level issues, you can do the following:
96
+
97
+ ```ruby
98
+ Vault.with_retries(Vault::HTTPConnectionError) do
99
+ Vault.logical.read("secret/on_bad_network")
100
+ end
101
+ ```
102
+
103
+ To rescue particular HTTP exceptions:
104
+
105
+ ```ruby
106
+ # Rescue 4xx errors
107
+ Vault.with_retries(Vault::HTTPClientError) {}
108
+
109
+ # Rescue 5xx errors
110
+ Vault.with_retries(Vault::HTTPServerError) {}
111
+
112
+ # Rescue all HTTP errors
113
+ Vault.with_retries(Vault::HTTPError) {}
114
+ ```
115
+
116
+ For advanced users, the first argument of the block is the attempt number and the second argument is the exception itself:
117
+
118
+ ```ruby
119
+ Vault.with_retries(Vault::HTTPConnectionError, Vault::HTTPError) do |attempt, e|
120
+ log "Received exception #{e} from Vault - attempt #{attempt}"
121
+ Vault.logical.read("secret/bacon")
122
+ end
123
+ ```
124
+
125
+ The following options are available:
126
+
127
+ ```ruby
128
+ # :attempts - The number of retries when communicating with the Vault server.
129
+ # The default value is 2.
130
+ #
131
+ # :base - The base interval for retry exponential backoff. The default value is
132
+ # 0.05s.
133
+ #
134
+ # :max_wait - The maximum amount of time for a single exponential backoff to
135
+ # sleep. The default value is 2.0s.
136
+
137
+ Vault.with_retries(Vault::HTTPError, attempts: 5) do
138
+ # ...
139
+ end
140
+ ```
141
+
142
+ After the number of retries have been exhausted, the original exception is raised.
143
+
144
+ ```ruby
145
+ Vault.with_retries(Exception) do
146
+ raise Exception
147
+ end #=> #<Exception>
148
+ ```
149
+
150
+ #### Seal Status
151
+ ```ruby
152
+ Vault.sys.seal_status
153
+ #=> #<Vault::SealStatus sealed=false, t=1, n=1, progress=0>
154
+ ```
155
+
156
+ #### Create a Secret
157
+ ```ruby
158
+ Vault.logical.write("secret/bacon", delicious: true, cooktime: "11")
159
+ #=> #<Vault::Secret lease_id="">
160
+ ```
161
+
162
+ #### Retrieve a Secret
163
+ ```ruby
164
+ Vault.logical.read("secret/bacon")
165
+ #=> #<Vault::Secret lease_id="">
166
+ ```
167
+
168
+ #### Retrieve the Contents of a Secret
169
+ ```ruby
170
+ secret = Vault.logical.read("secret/bacon")
171
+ secret.data #=> { :cooktime = >"11", :delicious => true }
172
+ ```
173
+
174
+ ### Response wrapping
175
+
176
+ ```ruby
177
+ # Request new access token as wrapped response where the TTL of the temporary
178
+ # token is "5s".
179
+ wrapped = Vault.auth_token.create(wrap_ttl: "5s")
180
+
181
+ # Unwrap the wrapped response to get the final token using the initial temporary
182
+ # token from the first request.
183
+ unwrapped = Vault.logical.unwrap(wrapped.wrap_info.token)
184
+
185
+ # Extract the final token from the response.
186
+ token = unwrapped.data.auth.client_token
187
+ ```
188
+
189
+ A helper function is also provided when unwrapping a token directly:
190
+
191
+ ```ruby
192
+ # Request new access token as wrapped response where the TTL of the temporary
193
+ # token is "5s".
194
+ wrapped = Vault.auth_token.create(wrap_ttl: "5s")
195
+
196
+ # Unwrap wrapped response for final token using the initial temporary token.
197
+ token = Vault.logical.unwrap_token(wrapped)
198
+ ```
199
+
200
+
201
+ Development
202
+ -----------
203
+ 1. Clone the project on GitHub
204
+ 2. Create a feature branch
205
+ 3. Submit a Pull Request
206
+
207
+ Important Notes:
208
+
209
+ - **All new features must include test coverage.** At a bare minimum, Unit tests are required. It is preferred if you include acceptance tests as well.
210
+ - **The tests must be be idempotent.** The HTTP calls made during a test should be able to be run over and over.
211
+ - **Tests are order independent.** The default RSpec configuration randomizes the test order, so this should not be a problem.
212
+ - **Integration tests require Vault** Vault must be available in the path for the integration tests to pass.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/lib/vault.rb ADDED
@@ -0,0 +1,49 @@
1
+ module Vault
2
+ require_relative "vault/errors"
3
+ require_relative "vault/client"
4
+ require_relative "vault/configurable"
5
+ require_relative "vault/defaults"
6
+ require_relative "vault/response"
7
+ require_relative "vault/version"
8
+
9
+ require_relative "vault/api"
10
+
11
+ class << self
12
+ # API client object based off the configured options in {Configurable}.
13
+ #
14
+ # @return [Vault::Client]
15
+ attr_reader :client
16
+
17
+ def setup!
18
+ @client = Vault::Client.new
19
+
20
+ # Set secure SSL options
21
+ OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options].tap do |opts|
22
+ opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS)
23
+ opts |= OpenSSL::SSL::OP_NO_COMPRESSION if defined?(OpenSSL::SSL::OP_NO_COMPRESSION)
24
+ opts |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2)
25
+ opts |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3)
26
+ end
27
+
28
+ self
29
+ end
30
+
31
+ # Delegate all methods to the client object, essentially making the module
32
+ # object behave like a {Client}.
33
+ def method_missing(m, *args, &block)
34
+ if @client.respond_to?(m)
35
+ @client.send(m, *args, &block)
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ # Delegating +respond_to+ to the {Client}.
42
+ def respond_to_missing?(m, include_private = false)
43
+ @client.respond_to?(m, include_private) || super
44
+ end
45
+ end
46
+ end
47
+
48
+ # Load the initial default values
49
+ Vault.setup!
data/lib/vault/api.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Vault
2
+ module API
3
+ require_relative "api/approle"
4
+ require_relative "api/auth_token"
5
+ require_relative "api/auth_tls"
6
+ require_relative "api/auth"
7
+ require_relative "api/help"
8
+ require_relative "api/kv"
9
+ require_relative "api/logical"
10
+ require_relative "api/secret"
11
+ require_relative "api/sys"
12
+ end
13
+ end
@@ -0,0 +1,218 @@
1
+ require "json"
2
+
3
+ require_relative "secret"
4
+ require_relative "../client"
5
+ require_relative "../request"
6
+ require_relative "../response"
7
+
8
+ module Vault
9
+ class Client
10
+ # A proxy to the {AppRole} methods.
11
+ # @return [AppRole]
12
+ def approle
13
+ @approle ||= AppRole.new(self)
14
+ end
15
+ end
16
+
17
+ class AppRole < Request
18
+ # Creates a new AppRole or update an existing AppRole with the given name
19
+ # and attributes.
20
+ #
21
+ # @example
22
+ # Vault.approle.set_role("testrole", {
23
+ # secret_id_ttl: "10m",
24
+ # token_ttl: "20m",
25
+ # policies: "default",
26
+ # period: 3600,
27
+ # }) #=> true
28
+ #
29
+ # @param [String] name
30
+ # The name of the AppRole
31
+ # @param [Hash] options
32
+ # @option options [Boolean] :bind_secret_id
33
+ # Require secret_id to be presented when logging in using this AppRole.
34
+ # @option options [String] :bound_cidr_list
35
+ # Comma-separated list of CIDR blocks. Specifies blocks of IP addresses
36
+ # which can perform the login operation.
37
+ # @option options [String] :policies
38
+ # Comma-separated list of policies set on tokens issued via this AppRole.
39
+ # @option options [String] :secret_id_num_uses
40
+ # Number of times any particular SecretID can be used to fetch a token
41
+ # from this AppRole, after which the SecretID will expire.
42
+ # @option options [Fixnum, String] :secret_id_ttl
43
+ # The number of seconds or a golang-formatted timestamp like "60m" after
44
+ # which any SecretID expires.
45
+ # @option options [Fixnum, String] :token_ttl
46
+ # The number of seconds or a golang-formatted timestamp like "60m" to set
47
+ # as the TTL for issued tokens and at renewal time.
48
+ # @option options [Fixnum, String] :token_max_ttl
49
+ # The number of seconds or a golang-formatted timestamp like "60m" after
50
+ # which the issued token can no longer be renewed.
51
+ # @option options [Fixnum, String] :period
52
+ # The number of seconds or a golang-formatted timestamp like "60m".
53
+ # If set, the token generated using this AppRole is a periodic token.
54
+ # So long as it is renewed it never expires, but the TTL set on the token
55
+ # at each renewal is fixed to the value specified here. If this value is
56
+ # modified, the token will pick up the new value at its next renewal.
57
+ #
58
+ # @return [true]
59
+ def set_role(name, options = {})
60
+ headers = extract_headers!(options)
61
+ client.post("/v1/auth/approle/role/#{encode_path(name)}", JSON.fast_generate(options), headers)
62
+ return true
63
+ end
64
+
65
+ # Gets the AppRole by the given name. If an AppRole does not exist by that
66
+ # name, +nil+ is returned.
67
+ #
68
+ # @example
69
+ # Vault.approle.role("testrole") #=> #<Vault::Secret lease_id="...">
70
+ #
71
+ # @return [Secret, nil]
72
+ def role(name)
73
+ json = client.get("/v1/auth/approle/role/#{encode_path(name)}")
74
+ return Secret.decode(json)
75
+ rescue HTTPError => e
76
+ return nil if e.code == 404
77
+ raise
78
+ end
79
+
80
+ # Gets the list of AppRoles in vault auth backend.
81
+ #
82
+ # @example
83
+ # Vault.approle.roles #=> ["testrole"]
84
+ #
85
+ # @return [Array<String>]
86
+ def roles(options = {})
87
+ headers = extract_headers!(options)
88
+ json = client.list("/v1/auth/approle/role", options, headers)
89
+ return Secret.decode(json).data[:keys] || []
90
+ rescue HTTPError => e
91
+ return [] if e.code == 404
92
+ raise
93
+ end
94
+
95
+ # Reads the RoleID of an existing AppRole. If an AppRole does not exist by
96
+ # that name, +nil+ is returned.
97
+ #
98
+ # @example
99
+ # Vault.approle.role_id("testrole") #=> #<Vault::Secret lease_id="...">
100
+ #
101
+ # @return [Secret, nil]
102
+ def role_id(name)
103
+ json = client.get("/v1/auth/approle/role/#{encode_path(name)}/role-id")
104
+ return Secret.decode(json).data[:role_id]
105
+ rescue HTTPError => e
106
+ return nil if e.code == 404
107
+ raise
108
+ end
109
+
110
+ # Updates the RoleID of an existing AppRole to a custom value.
111
+ #
112
+ # @example
113
+ # Vault.approle.set_role_id("testrole") #=> true
114
+ #
115
+ # @return [true]
116
+ def set_role_id(name, role_id)
117
+ options = { role_id: role_id }
118
+ client.post("/v1/auth/approle/role/#{encode_path(name)}/role-id", JSON.fast_generate(options))
119
+ return true
120
+ end
121
+
122
+ # Deletes the AppRole with the given name. If an AppRole does not exist,
123
+ # vault will not return an error.
124
+ #
125
+ # @example
126
+ # Vault.approle.delete_role("testrole") #=> true
127
+ #
128
+ # @param [String] name
129
+ # the name of the certificate
130
+ def delete_role(name)
131
+ client.delete("/v1/auth/approle/role/#{encode_path(name)}")
132
+ return true
133
+ end
134
+
135
+ # Generates and issues a new SecretID on an existing AppRole.
136
+ #
137
+ # @example Generate a new SecretID
138
+ # result = Vault.approle.create_secret_id("testrole") #=> #<Vault::Secret lease_id="...">
139
+ # result.data[:secret_id] #=> "841771dc-11c9-bbc7-bcac-6a3945a69cd9"
140
+ #
141
+ # @example Assign a custom SecretID
142
+ # result = Vault.approle.create_secret_id("testrole", {
143
+ # secret_id: "testsecretid"
144
+ # }) #=> #<Vault::Secret lease_id="...">
145
+ # result.data[:secret_id] #=> "testsecretid"
146
+ #
147
+ # @param [String] role_name
148
+ # The name of the AppRole
149
+ # @param [Hash] options
150
+ # @option options [String] :secret_id
151
+ # SecretID to be attached to the Role. If not set, then the new SecretID
152
+ # will be generated
153
+ # @option options [Hash<String, String>] :metadata
154
+ # Metadata to be tied to the SecretID. This should be a JSON-formatted
155
+ # string containing the metadata in key-value pairs. It will be set on
156
+ # tokens issued with this SecretID, and is logged in audit logs in
157
+ # plaintext.
158
+ #
159
+ # @return [true]
160
+ def create_secret_id(role_name, options = {})
161
+ headers = extract_headers!(options)
162
+ if options[:secret_id]
163
+ json = client.post("/v1/auth/approle/role/#{encode_path(role_name)}/custom-secret-id", JSON.fast_generate(options), headers)
164
+ else
165
+ json = client.post("/v1/auth/approle/role/#{encode_path(role_name)}/secret-id", JSON.fast_generate(options), headers)
166
+ end
167
+ return Secret.decode(json)
168
+ end
169
+
170
+ # Reads out the properties of a SecretID assigned to an AppRole.
171
+ # If the specified SecretID don't exist, +nil+ is returned.
172
+ #
173
+ # @example
174
+ # Vault.approle.role("testrole", "841771dc-11c9-...") #=> #<Vault::Secret lease_id="...">
175
+ #
176
+ # @param [String] role_name
177
+ # The name of the AppRole
178
+ # @param [String] secret_id
179
+ # SecretID belonging to AppRole
180
+ #
181
+ # @return [Secret, nil]
182
+ def secret_id(role_name, secret_id)
183
+ opts = { secret_id: secret_id }
184
+ json = client.post("/v1/auth/approle/role/#{encode_path(role_name)}/secret-id/lookup", JSON.fast_generate(opts), {})
185
+ return nil unless json
186
+ return Secret.decode(json)
187
+ rescue HTTPError => e
188
+ if e.code == 404 || e.code == 405
189
+ begin
190
+ json = client.get("/v1/auth/approle/role/#{encode_path(role_name)}/secret-id/#{encode_path(secret_id)}")
191
+ return Secret.decode(json)
192
+ rescue HTTPError => e
193
+ return nil if e.code == 404
194
+ raise e
195
+ end
196
+ end
197
+
198
+ raise
199
+ end
200
+
201
+ # Lists the accessors of all the SecretIDs issued against the AppRole.
202
+ # This includes the accessors for "custom" SecretIDs as well. If there are
203
+ # no SecretIDs against this role, an empty array will be returned.
204
+ #
205
+ # @example
206
+ # Vault.approle.secret_ids("testrole") #=> ["ce102d2a-...", "a1c8dee4-..."]
207
+ #
208
+ # @return [Array<String>]
209
+ def secret_id_accessors(role_name, options = {})
210
+ headers = extract_headers!(options)
211
+ json = client.list("/v1/auth/approle/role/#{encode_path(role_name)}/secret-id", options, headers)
212
+ return Secret.decode(json).data[:keys] || []
213
+ rescue HTTPError => e
214
+ return [] if e.code == 404
215
+ raise
216
+ end
217
+ end
218
+ end