vault_ruby_client 0.18.2

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