vault_ruby_client 0.18.2

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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +287 -0
  3. data/LICENSE +364 -0
  4. data/README.md +223 -0
  5. data/lib/vault/api/approle.rb +221 -0
  6. data/lib/vault/api/auth.rb +324 -0
  7. data/lib/vault/api/auth_tls.rb +95 -0
  8. data/lib/vault/api/auth_token.rb +245 -0
  9. data/lib/vault/api/help.rb +36 -0
  10. data/lib/vault/api/kv.rb +230 -0
  11. data/lib/vault/api/logical.rb +153 -0
  12. data/lib/vault/api/secret.rb +171 -0
  13. data/lib/vault/api/sys/audit.rb +94 -0
  14. data/lib/vault/api/sys/auth.rb +119 -0
  15. data/lib/vault/api/sys/health.rb +66 -0
  16. data/lib/vault/api/sys/init.rb +86 -0
  17. data/lib/vault/api/sys/leader.rb +51 -0
  18. data/lib/vault/api/sys/lease.rb +52 -0
  19. data/lib/vault/api/sys/mount.rb +165 -0
  20. data/lib/vault/api/sys/namespace.rb +86 -0
  21. data/lib/vault/api/sys/policy.rb +95 -0
  22. data/lib/vault/api/sys/quota.rb +110 -0
  23. data/lib/vault/api/sys/seal.rb +84 -0
  24. data/lib/vault/api/sys.rb +30 -0
  25. data/lib/vault/api/transform/alphabet.rb +46 -0
  26. data/lib/vault/api/transform/role.rb +45 -0
  27. data/lib/vault/api/transform/template.rb +57 -0
  28. data/lib/vault/api/transform/transformation.rb +64 -0
  29. data/lib/vault/api/transform.rb +32 -0
  30. data/lib/vault/api.rb +17 -0
  31. data/lib/vault/client.rb +460 -0
  32. data/lib/vault/configurable.rb +53 -0
  33. data/lib/vault/defaults.rb +218 -0
  34. data/lib/vault/encode.rb +22 -0
  35. data/lib/vault/errors.rb +87 -0
  36. data/lib/vault/persistent/connection.rb +45 -0
  37. data/lib/vault/persistent/pool.rb +51 -0
  38. data/lib/vault/persistent/timed_stack_multi.rb +73 -0
  39. data/lib/vault/persistent.rb +1161 -0
  40. data/lib/vault/request.rb +47 -0
  41. data/lib/vault/response.rb +92 -0
  42. data/lib/vault/vendor/connection_pool/timed_stack.rb +181 -0
  43. data/lib/vault/vendor/connection_pool/version.rb +8 -0
  44. data/lib/vault/vendor/connection_pool.rb +153 -0
  45. data/lib/vault/version.rb +6 -0
  46. data/lib/vault_ruby_client.rb +53 -0
  47. metadata +158 -0
data/README.md ADDED
@@ -0,0 +1,223 @@
1
+ # Vault Ruby Client
2
+
3
+ [![Gem Version](https://img.shields.io/gem/v/vault_ruby_client.svg?style=flat)](https://rubygems.org/gems/vault_ruby_client)
4
+ [![Build Status](https://github.com/khiav223577/vault_ruby_client/workflows/Ruby/badge.svg)](https://github.com/khiav223577/vault_ruby_client/actions)
5
+ [![RubyGems](http://img.shields.io/gem/dt/vault_ruby_client.svg?style=flat)](https://rubygems.org/gems/vault_ruby_client)
6
+ [![Code Climate](https://codeclimate.com/github/khiav223577/vault_ruby_client/badges/gpa.svg)](https://codeclimate.com/github/khiav223577/vault_ruby_client)
7
+ [![Test Coverage](https://codeclimate.com/github/khiav223577/vault_ruby_client/badges/coverage.svg)](https://codeclimate.com/github/khiav223577/vault_ruby_client/coverage)
8
+
9
+ Vault is the official Ruby client for interacting with [Vault](https://vaultproject.io) by HashiCorp.
10
+
11
+ Quick Start
12
+ -----------
13
+ Install Ruby 2.0+: [Guide](https://www.ruby-lang.org/en/documentation/installation/).
14
+
15
+ > Please note that as of Vault Ruby version 0.14.0 versions of Ruby prior to 2.0 are no longer supported.
16
+
17
+ Install via Rubygems:
18
+
19
+ $ gem install vault_ruby_client
20
+
21
+ or add it to your Gemfile if you're using Bundler:
22
+
23
+ ```ruby
24
+ gem "vault_ruby_client"
25
+ ```
26
+
27
+ and then run the `bundle` command to install.
28
+
29
+ Start a Vault client:
30
+
31
+ ```ruby
32
+ Vault.address = "http://127.0.0.1:8200" # Also reads from ENV["VAULT_ADDR"]
33
+ Vault.token = "abcd-1234" # Also reads from ENV["VAULT_TOKEN"]
34
+ # Optional - if using the Namespace enterprise feature
35
+ # Vault.namespace = "my-namespace" # Also reads from ENV["VAULT_NAMESPACE"]
36
+
37
+ Vault.sys.mounts #=> { :secret => #<struct Vault::Mount type="generic", description="generic secret storage"> }
38
+ ```
39
+
40
+ Usage
41
+ -----
42
+ The following configuration options are available:
43
+
44
+ ```ruby
45
+ Vault.configure do |config|
46
+ # The address of the Vault server, also read as ENV["VAULT_ADDR"]
47
+ config.address = "https://127.0.0.1:8200"
48
+
49
+ # The token to authenticate with Vault, also read as ENV["VAULT_TOKEN"]
50
+ config.token = "abcd-1234"
51
+ # Optional - if using the Namespace enterprise feature
52
+ # config.namespace = "my-namespace" # Also reads from ENV["VAULT_NAMESPACE"]
53
+
54
+ # Proxy connection information, also read as ENV["VAULT_PROXY_(thing)"]
55
+ config.proxy_address = "..."
56
+ config.proxy_port = "..."
57
+ config.proxy_username = "..."
58
+ config.proxy_password = "..."
59
+
60
+ # Custom SSL PEM, also read as ENV["VAULT_SSL_CERT"]
61
+ config.ssl_pem_file = "/path/on/disk.pem"
62
+
63
+ # As an alternative to a pem file, you can provide the raw PEM string, also read in the following order of preference:
64
+ # ENV["VAULT_SSL_PEM_CONTENTS_BASE64"] then ENV["VAULT_SSL_PEM_CONTENTS"]
65
+ config.ssl_pem_contents = "-----BEGIN ENCRYPTED..."
66
+
67
+ # Use SSL verification, also read as ENV["VAULT_SSL_VERIFY"]
68
+ config.ssl_verify = false
69
+
70
+ # Timeout the connection after a certain amount of time (seconds), also read
71
+ # as ENV["VAULT_TIMEOUT"]
72
+ config.timeout = 30
73
+
74
+ # It is also possible to have finer-grained controls over the timeouts, these
75
+ # may also be read as environment variables
76
+ config.ssl_timeout = 5
77
+ config.open_timeout = 5
78
+ config.read_timeout = 30
79
+ end
80
+ ```
81
+
82
+ 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:
83
+
84
+ ```ruby
85
+ client_1 = Vault::Client.new(address: "https://vault.mycompany.com")
86
+ client_2 = Vault::Client.new(address: "https://other-vault.mycompany.com")
87
+ ```
88
+
89
+ And if you want to authenticate with a `AWS EC2` :
90
+
91
+ ```ruby
92
+ # Export VAULT_ADDR to ENV then
93
+ # Get the pkcs7 value from AWS
94
+ signature = `curl http://169.254.169.254/latest/dynamic/instance-identity/pkcs7`
95
+ iam_role = `curl http://169.254.169.254/latest/meta-data/iam/security-credentials/`
96
+ vault_token = Vault.auth.aws_ec2(iam_role, signature, nil)
97
+ vault_client = Vault::Client.new(address: ENV["VAULT_ADDR"], token: vault_token.auth.client_token)
98
+ ```
99
+
100
+ ### Making requests
101
+ 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.
102
+
103
+ 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:
104
+
105
+ ```ruby
106
+ Vault.with_retries(Vault::HTTPConnectionError) do
107
+ Vault.logical.read("secret/on_bad_network")
108
+ end
109
+ ```
110
+
111
+ To rescue particular HTTP exceptions:
112
+
113
+ ```ruby
114
+ # Rescue 4xx errors
115
+ Vault.with_retries(Vault::HTTPClientError) {}
116
+
117
+ # Rescue 5xx errors
118
+ Vault.with_retries(Vault::HTTPServerError) {}
119
+
120
+ # Rescue all HTTP errors
121
+ Vault.with_retries(Vault::HTTPError) {}
122
+ ```
123
+
124
+ For advanced users, the first argument of the block is the attempt number and the second argument is the exception itself:
125
+
126
+ ```ruby
127
+ Vault.with_retries(Vault::HTTPConnectionError, Vault::HTTPError) do |attempt, e|
128
+ if e
129
+ log "Received exception #{e} from Vault - attempt #{attempt}"
130
+ end
131
+ Vault.logical.read("secret/bacon")
132
+ end
133
+ ```
134
+
135
+ The following options are available:
136
+
137
+ ```ruby
138
+ # :attempts - The number of retries when communicating with the Vault server.
139
+ # The default value is 2.
140
+ #
141
+ # :base - The base interval for retry exponential backoff. The default value is
142
+ # 0.05s.
143
+ #
144
+ # :max_wait - The maximum amount of time for a single exponential backoff to
145
+ # sleep. The default value is 2.0s.
146
+
147
+ Vault.with_retries(Vault::HTTPError, attempts: 5) do
148
+ # ...
149
+ end
150
+ ```
151
+
152
+ After the number of retries have been exhausted, the original exception is raised.
153
+
154
+ ```ruby
155
+ Vault.with_retries(Exception) do
156
+ raise Exception
157
+ end #=> #<Exception>
158
+ ```
159
+
160
+ #### Seal Status
161
+ ```ruby
162
+ Vault.sys.seal_status
163
+ #=> #<Vault::SealStatus sealed=false, t=1, n=1, progress=0>
164
+ ```
165
+
166
+ #### Create a Secret
167
+ ```ruby
168
+ Vault.logical.write("secret/bacon", delicious: true, cooktime: "11")
169
+ #=> #<Vault::Secret lease_id="">
170
+ ```
171
+
172
+ #### Retrieve a Secret
173
+ ```ruby
174
+ Vault.logical.read("secret/bacon")
175
+ #=> #<Vault::Secret lease_id="">
176
+ ```
177
+
178
+ #### Retrieve the Contents of a Secret
179
+ ```ruby
180
+ secret = Vault.logical.read("secret/bacon")
181
+ secret.data #=> { :cooktime = >"11", :delicious => true }
182
+ ```
183
+
184
+ ### Response wrapping
185
+
186
+ ```ruby
187
+ # Request new access token as wrapped response where the TTL of the temporary
188
+ # token is "5s".
189
+ wrapped = Vault.auth_token.create(wrap_ttl: "5s")
190
+
191
+ # Unwrap the wrapped response to get the final token using the initial temporary
192
+ # token from the first request.
193
+ unwrapped = Vault.logical.unwrap(wrapped.wrap_info.token)
194
+
195
+ # Extract the final token from the response.
196
+ token = unwrapped.data.auth.client_token
197
+ ```
198
+
199
+ A helper function is also provided when unwrapping a token directly:
200
+
201
+ ```ruby
202
+ # Request new access token as wrapped response where the TTL of the temporary
203
+ # token is "5s".
204
+ wrapped = Vault.auth_token.create(wrap_ttl: "5s")
205
+
206
+ # Unwrap wrapped response for final token using the initial temporary token.
207
+ token = Vault.logical.unwrap_token(wrapped)
208
+ ```
209
+
210
+
211
+ Development
212
+ -----------
213
+ 1. Clone the project on GitHub
214
+ 2. Create a feature branch
215
+ 3. Submit a Pull Request
216
+
217
+ Important Notes:
218
+
219
+ - **All new features must include test coverage.** At a bare minimum, Unit tests are required. It is preferred if you include integration tests as well.
220
+ - **The tests must be idempotent.** The HTTP calls made during a test should be able to be run over and over.
221
+ - **Tests are order independent.** The default RSpec configuration randomizes the test order, so this should not be a problem.
222
+ - **Integration tests require Vault** Vault must be available in the path for the integration tests to pass.
223
+ - **In order to be considered an integration test:** The test MUST use the `vault_test_client` or `vault_redirect_test_client` as the client. This spawns a process, or uses an already existing process from another test, to run against.
@@ -0,0 +1,221 @@
1
+ # Copyright (c) HashiCorp, Inc.
2
+ # SPDX-License-Identifier: MPL-2.0
3
+
4
+ require "json"
5
+
6
+ require_relative "secret"
7
+ require_relative "../client"
8
+ require_relative "../request"
9
+ require_relative "../response"
10
+
11
+ module Vault
12
+ class Client
13
+ # A proxy to the {AppRole} methods.
14
+ # @return [AppRole]
15
+ def approle
16
+ @approle ||= AppRole.new(self)
17
+ end
18
+ end
19
+
20
+ class AppRole < Request
21
+ # Creates a new AppRole or update an existing AppRole with the given name
22
+ # and attributes.
23
+ #
24
+ # @example
25
+ # Vault.approle.set_role("testrole", {
26
+ # secret_id_ttl: "10m",
27
+ # token_ttl: "20m",
28
+ # policies: "default",
29
+ # period: 3600,
30
+ # }) #=> true
31
+ #
32
+ # @param [String] name
33
+ # The name of the AppRole
34
+ # @param [Hash] options
35
+ # @option options [Boolean] :bind_secret_id
36
+ # Require secret_id to be presented when logging in using this AppRole.
37
+ # @option options [String] :bound_cidr_list
38
+ # Comma-separated list of CIDR blocks. Specifies blocks of IP addresses
39
+ # which can perform the login operation.
40
+ # @option options [String] :policies
41
+ # Comma-separated list of policies set on tokens issued via this AppRole.
42
+ # @option options [String] :secret_id_num_uses
43
+ # Number of times any particular SecretID can be used to fetch a token
44
+ # from this AppRole, after which the SecretID will expire.
45
+ # @option options [Fixnum, String] :secret_id_ttl
46
+ # The number of seconds or a golang-formatted timestamp like "60m" after
47
+ # which any SecretID expires.
48
+ # @option options [Fixnum, String] :token_ttl
49
+ # The number of seconds or a golang-formatted timestamp like "60m" to set
50
+ # as the TTL for issued tokens and at renewal time.
51
+ # @option options [Fixnum, String] :token_max_ttl
52
+ # The number of seconds or a golang-formatted timestamp like "60m" after
53
+ # which the issued token can no longer be renewed.
54
+ # @option options [Fixnum, String] :period
55
+ # The number of seconds or a golang-formatted timestamp like "60m".
56
+ # If set, the token generated using this AppRole is a periodic token.
57
+ # So long as it is renewed it never expires, but the TTL set on the token
58
+ # at each renewal is fixed to the value specified here. If this value is
59
+ # modified, the token will pick up the new value at its next renewal.
60
+ #
61
+ # @return [true]
62
+ def set_role(name, options = {})
63
+ headers = extract_headers!(options)
64
+ client.post("/v1/auth/approle/role/#{encode_path(name)}", JSON.fast_generate(options), headers)
65
+ return true
66
+ end
67
+
68
+ # Gets the AppRole by the given name. If an AppRole does not exist by that
69
+ # name, +nil+ is returned.
70
+ #
71
+ # @example
72
+ # Vault.approle.role("testrole") #=> #<Vault::Secret lease_id="...">
73
+ #
74
+ # @return [Secret, nil]
75
+ def role(name)
76
+ json = client.get("/v1/auth/approle/role/#{encode_path(name)}")
77
+ return Secret.decode(json)
78
+ rescue HTTPError => e
79
+ return nil if e.code == 404
80
+ raise
81
+ end
82
+
83
+ # Gets the list of AppRoles in vault auth backend.
84
+ #
85
+ # @example
86
+ # Vault.approle.roles #=> ["testrole"]
87
+ #
88
+ # @return [Array<String>]
89
+ def roles(options = {})
90
+ headers = extract_headers!(options)
91
+ json = client.list("/v1/auth/approle/role", options, headers)
92
+ return Secret.decode(json).data[:keys] || []
93
+ rescue HTTPError => e
94
+ return [] if e.code == 404
95
+ raise
96
+ end
97
+
98
+ # Reads the RoleID of an existing AppRole. If an AppRole does not exist by
99
+ # that name, +nil+ is returned.
100
+ #
101
+ # @example
102
+ # Vault.approle.role_id("testrole") #=> #<Vault::Secret lease_id="...">
103
+ #
104
+ # @return [Secret, nil]
105
+ def role_id(name)
106
+ json = client.get("/v1/auth/approle/role/#{encode_path(name)}/role-id")
107
+ return Secret.decode(json).data[:role_id]
108
+ rescue HTTPError => e
109
+ return nil if e.code == 404
110
+ raise
111
+ end
112
+
113
+ # Updates the RoleID of an existing AppRole to a custom value.
114
+ #
115
+ # @example
116
+ # Vault.approle.set_role_id("testrole") #=> true
117
+ #
118
+ # @return [true]
119
+ def set_role_id(name, role_id)
120
+ options = { role_id: role_id }
121
+ client.post("/v1/auth/approle/role/#{encode_path(name)}/role-id", JSON.fast_generate(options))
122
+ return true
123
+ end
124
+
125
+ # Deletes the AppRole with the given name. If an AppRole does not exist,
126
+ # vault will not return an error.
127
+ #
128
+ # @example
129
+ # Vault.approle.delete_role("testrole") #=> true
130
+ #
131
+ # @param [String] name
132
+ # the name of the certificate
133
+ def delete_role(name)
134
+ client.delete("/v1/auth/approle/role/#{encode_path(name)}")
135
+ return true
136
+ end
137
+
138
+ # Generates and issues a new SecretID on an existing AppRole.
139
+ #
140
+ # @example Generate a new SecretID
141
+ # result = Vault.approle.create_secret_id("testrole") #=> #<Vault::Secret lease_id="...">
142
+ # result.data[:secret_id] #=> "841771dc-11c9-bbc7-bcac-6a3945a69cd9"
143
+ #
144
+ # @example Assign a custom SecretID
145
+ # result = Vault.approle.create_secret_id("testrole", {
146
+ # secret_id: "testsecretid"
147
+ # }) #=> #<Vault::Secret lease_id="...">
148
+ # result.data[:secret_id] #=> "testsecretid"
149
+ #
150
+ # @param [String] role_name
151
+ # The name of the AppRole
152
+ # @param [Hash] options
153
+ # @option options [String] :secret_id
154
+ # SecretID to be attached to the Role. If not set, then the new SecretID
155
+ # will be generated
156
+ # @option options [Hash<String, String>] :metadata
157
+ # Metadata to be tied to the SecretID. This should be a JSON-formatted
158
+ # string containing the metadata in key-value pairs. It will be set on
159
+ # tokens issued with this SecretID, and is logged in audit logs in
160
+ # plaintext.
161
+ #
162
+ # @return [true]
163
+ def create_secret_id(role_name, options = {})
164
+ headers = extract_headers!(options)
165
+ if options[:secret_id]
166
+ json = client.post("/v1/auth/approle/role/#{encode_path(role_name)}/custom-secret-id", JSON.fast_generate(options), headers)
167
+ else
168
+ json = client.post("/v1/auth/approle/role/#{encode_path(role_name)}/secret-id", JSON.fast_generate(options), headers)
169
+ end
170
+ return Secret.decode(json)
171
+ end
172
+
173
+ # Reads out the properties of a SecretID assigned to an AppRole.
174
+ # If the specified SecretID don't exist, +nil+ is returned.
175
+ #
176
+ # @example
177
+ # Vault.approle.role("testrole", "841771dc-11c9-...") #=> #<Vault::Secret lease_id="...">
178
+ #
179
+ # @param [String] role_name
180
+ # The name of the AppRole
181
+ # @param [String] secret_id
182
+ # SecretID belonging to AppRole
183
+ #
184
+ # @return [Secret, nil]
185
+ def secret_id(role_name, secret_id)
186
+ opts = { secret_id: secret_id }
187
+ json = client.post("/v1/auth/approle/role/#{encode_path(role_name)}/secret-id/lookup", JSON.fast_generate(opts), {})
188
+ return nil unless json
189
+ return Secret.decode(json)
190
+ rescue HTTPError => e
191
+ if e.code == 404 || e.code == 405
192
+ begin
193
+ json = client.get("/v1/auth/approle/role/#{encode_path(role_name)}/secret-id/#{encode_path(secret_id)}")
194
+ return Secret.decode(json)
195
+ rescue HTTPError => e
196
+ return nil if e.code == 404
197
+ raise e
198
+ end
199
+ end
200
+
201
+ raise
202
+ end
203
+
204
+ # Lists the accessors of all the SecretIDs issued against the AppRole.
205
+ # This includes the accessors for "custom" SecretIDs as well. If there are
206
+ # no SecretIDs against this role, an empty array will be returned.
207
+ #
208
+ # @example
209
+ # Vault.approle.secret_ids("testrole") #=> ["ce102d2a-...", "a1c8dee4-..."]
210
+ #
211
+ # @return [Array<String>]
212
+ def secret_id_accessors(role_name, options = {})
213
+ headers = extract_headers!(options)
214
+ json = client.list("/v1/auth/approle/role/#{encode_path(role_name)}/secret-id", options, headers)
215
+ return Secret.decode(json).data[:keys] || []
216
+ rescue HTTPError => e
217
+ return [] if e.code == 404
218
+ raise
219
+ end
220
+ end
221
+ end