vault-kv 0.12.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.
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
@@ -0,0 +1,33 @@
1
+ require_relative "../client"
2
+ require_relative "../response"
3
+
4
+ module Vault
5
+ # Help is the response from a help query.
6
+ class Help < Response
7
+ # @!attribute [r] help
8
+ # The help information.
9
+ # @return [String]
10
+ field :help
11
+
12
+ # @!attribute [r] see_also
13
+ # Additional help documentation to see.
14
+ # @return [String]
15
+ field :see_also
16
+ end
17
+
18
+ class Client
19
+ # Gets help for the given path.
20
+ #
21
+ # @example
22
+ # Vault.help("secret") #=> #<Vault::Help help="..." see_also="...">
23
+ #
24
+ # @param [String] path
25
+ # the path to get help for
26
+ #
27
+ # @return [Help]
28
+ def help(path)
29
+ json = self.get("/v1/#{EncodePath.encode_path(path)}", help: 1)
30
+ return Help.decode(json)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,207 @@
1
+ require_relative "secret"
2
+ require_relative "../client"
3
+ require_relative "../request"
4
+ require_relative "../response"
5
+
6
+ module Vault
7
+ class Client
8
+ # A proxy to the {KV} methods.
9
+ # @return [KV]
10
+ def kv(mount)
11
+ KV.new(self, mount)
12
+ end
13
+ end
14
+
15
+ class KV < Request
16
+ attr_reader :mount
17
+
18
+ def initialize(client, mount)
19
+ super client
20
+
21
+ @mount = mount
22
+ end
23
+
24
+ # List the names of secrets at the given path, if the path supports
25
+ # listing. If the the path does not exist, an empty array will be returned.
26
+ #
27
+ # @example
28
+ # Vault.kv("secret").list("foo") #=> ["bar", "baz"]
29
+ #
30
+ # @param [String] path
31
+ # the path to list
32
+ #
33
+ # @return [Array<String>]
34
+ def list(path = "", options = {})
35
+ headers = extract_headers!(options)
36
+ json = client.list("/v1/#{mount}/metadata/#{encode_path(path)}", {}, headers)
37
+ json[:data][:keys] || []
38
+ rescue HTTPError => e
39
+ return [] if e.code == 404
40
+ raise
41
+ end
42
+
43
+ # Read the secret at the given path. If the secret does not exist, +nil+
44
+ # will be returned. The latest version is returned by default, but you
45
+ # can request a specific version.
46
+ #
47
+ # @example
48
+ # Vault.kv("secret").read("password") #=> #<Vault::Secret lease_id="">
49
+ #
50
+ # @param [String] path
51
+ # the path to read
52
+ # @param [Integer] version
53
+ # the version of the secret
54
+ #
55
+ # @return [Secret, nil]
56
+ def read(path, version = nil, options = {})
57
+ headers = extract_headers!(options)
58
+ params = {}
59
+ params[:version] = version unless version.nil?
60
+
61
+ json = client.get("/v1/#{mount}/data/#{encode_path(path)}", params, headers)
62
+ return Secret.decode(json[:data])
63
+ rescue HTTPError => e
64
+ return nil if e.code == 404
65
+ raise
66
+ end
67
+
68
+ # Read the metadata of a secret at the given path. If the secret does not
69
+ # exist, nil will be returned.
70
+ #
71
+ # @example
72
+ # Vault.kv("secret").read_metadata("password") => {...}
73
+ #
74
+ # @param [String] path
75
+ # the path to read
76
+ #
77
+ # @return [Hash, nil]
78
+ def read_metadata(path)
79
+ client.get("/v1/#{mount}/metadata/#{encode_path(path)}")[:data]
80
+ rescue HTTPError => e
81
+ return nil if e.code == 404
82
+ raise
83
+ end
84
+
85
+ # Write the secret at the given path with the given data. Note that the
86
+ # data must be a {Hash}!
87
+ #
88
+ # @example
89
+ # Vault.logical.write("secret/password", value: "secret") #=> #<Vault::Secret lease_id="">
90
+ #
91
+ # @param [String] path
92
+ # the path to write
93
+ # @param [Hash] data
94
+ # the data to write
95
+ #
96
+ # @return [Secret]
97
+ def write(path, data = {}, options = {})
98
+ headers = extract_headers!(options)
99
+ json = client.post("/v1/#{mount}/data/#{encode_path(path)}", JSON.fast_generate(:data => data), headers)
100
+ if json.nil?
101
+ return true
102
+ else
103
+ return Secret.decode(json)
104
+ end
105
+ end
106
+
107
+ # Write the metadata of a secret at the given path. Note that teh data must
108
+ # be a {Hash}.
109
+ #
110
+ # @example
111
+ # Vault.kv("secret").write_metadata("password", max_versions => 3)
112
+ #
113
+ # @param [String] path
114
+ # the path to write
115
+ # @param [Hash] metadata
116
+ # the metadata to write
117
+ #
118
+ # @return [true]
119
+ def write_metadata(path, metadata = {})
120
+ client.post("/v1/#{mount}/metadata/#{encode_path(path)}", JSON.fast_generate(metadata))
121
+
122
+ true
123
+ end
124
+
125
+ # Delete the secret at the given path. If the secret does not exist, vault
126
+ # will still return true.
127
+ #
128
+ # @example
129
+ # Vault.logical.delete("secret/password") #=> true
130
+ #
131
+ # @param [String] path
132
+ # the path to delete
133
+ #
134
+ # @return [true]
135
+ def delete(path)
136
+ client.delete("/v1/#{mount}/data/#{encode_path(path)}")
137
+
138
+ true
139
+ end
140
+
141
+ # Mark specific versions of a secret as deleted.
142
+ #
143
+ # @example
144
+ # Vault.kv("secret").delete_versions("password", [1, 2])
145
+ #
146
+ # @param [String] path
147
+ # the path to remove versions from
148
+ # @param [Array<Integer>] versions
149
+ # an array of versions to remove
150
+ #
151
+ # @return [true]
152
+ def delete_versions(path, versions)
153
+ client.post("/v1/#{mount}/delete/#{encode_path(path)}", JSON.fast_generate(versions: versions))
154
+
155
+ true
156
+ end
157
+
158
+ # Mark specific versions of a secret as active.
159
+ #
160
+ # @example
161
+ # Vault.kv("secret").undelete_versions("password", [1, 2])
162
+ #
163
+ # @param [String] path
164
+ # the path to enable versions for
165
+ # @param [Array<Integer>] versions
166
+ # an array of versions to mark as undeleted
167
+ #
168
+ # @return [true]
169
+ def undelete_versions(path, versions)
170
+ client.post("/v1/#{mount}/undelete/#{encode_path(path)}", JSON.fast_generate(versions: versions))
171
+
172
+ true
173
+ end
174
+
175
+ # Completely remove a secret and its metadata.
176
+ #
177
+ # @example
178
+ # Vault.kv("secret").destroy("password")
179
+ #
180
+ # @param [String] path
181
+ # the path to remove
182
+ #
183
+ # @return [true]
184
+ def destroy(path)
185
+ client.delete("/v1/#{mount}/metadata/#{encode_path(path)}")
186
+
187
+ true
188
+ end
189
+
190
+ # Completely remove specific versions of a secret.
191
+ #
192
+ # @example
193
+ # Vault.kv("secret").destroy_versions("password", [1, 2])
194
+ #
195
+ # @param [String] path
196
+ # the path to remove versions from
197
+ # @param [Array<Integer>] versions
198
+ # an array of versions to destroy
199
+ #
200
+ # @return [true]
201
+ def destroy_versions(path, versions)
202
+ client.post("/v1/#{mount}/destroy/#{encode_path(path)}", JSON.fast_generate(versions: versions))
203
+
204
+ true
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,150 @@
1
+ require_relative "secret"
2
+ require_relative "../client"
3
+ require_relative "../request"
4
+ require_relative "../response"
5
+
6
+ module Vault
7
+ class Client
8
+ # A proxy to the {Logical} methods.
9
+ # @return [Logical]
10
+ def logical
11
+ @logical ||= Logical.new(self)
12
+ end
13
+ end
14
+
15
+ class Logical < Request
16
+ # List the secrets at the given path, if the path supports listing. If the
17
+ # the path does not exist, an exception will be raised.
18
+ #
19
+ # @example
20
+ # Vault.logical.list("secret") #=> [#<Vault::Secret>, #<Vault::Secret>, ...]
21
+ #
22
+ # @param [String] path
23
+ # the path to list
24
+ #
25
+ # @return [Array<String>]
26
+ def list(path, options = {})
27
+ headers = extract_headers!(options)
28
+ json = client.list("/v1/#{encode_path(path)}", {}, headers)
29
+ json[:data][:keys] || []
30
+ rescue HTTPError => e
31
+ return [] if e.code == 404
32
+ raise
33
+ end
34
+
35
+ # Read the secret at the given path. If the secret does not exist, +nil+
36
+ # will be returned.
37
+ #
38
+ # @example
39
+ # Vault.logical.read("secret/password") #=> #<Vault::Secret lease_id="">
40
+ #
41
+ # @param [String] path
42
+ # the path to read
43
+ #
44
+ # @return [Secret, nil]
45
+ def read(path, options = {})
46
+ headers = extract_headers!(options)
47
+ json = client.get("/v1/#{encode_path(path)}", {}, headers)
48
+ return Secret.decode(json)
49
+ rescue HTTPError => e
50
+ return nil if e.code == 404
51
+ raise
52
+ end
53
+
54
+ # Write the secret at the given path with the given data. Note that the
55
+ # data must be a {Hash}!
56
+ #
57
+ # @example
58
+ # Vault.logical.write("secret/password", value: "secret") #=> #<Vault::Secret lease_id="">
59
+ #
60
+ # @param [String] path
61
+ # the path to write
62
+ # @param [Hash] data
63
+ # the data to write
64
+ #
65
+ # @return [Secret]
66
+ def write(path, data = {}, options = {})
67
+ headers = extract_headers!(options)
68
+ json = client.put("/v1/#{encode_path(path)}", JSON.fast_generate(data), headers)
69
+ if json.nil?
70
+ return true
71
+ else
72
+ return Secret.decode(json)
73
+ end
74
+ end
75
+
76
+ # Delete the secret at the given path. If the secret does not exist, vault
77
+ # will still return true.
78
+ #
79
+ # @example
80
+ # Vault.logical.delete("secret/password") #=> true
81
+ #
82
+ # @param [String] path
83
+ # the path to delete
84
+ #
85
+ # @return [true]
86
+ def delete(path)
87
+ client.delete("/v1/#{encode_path(path)}")
88
+ return true
89
+ end
90
+
91
+ # Unwrap the data stored against the given token. If the secret does not
92
+ # exist, `nil` will be returned.
93
+ #
94
+ # @example
95
+ # Vault.logical.unwrap("f363dba8-25a7-08c5-430c-00b2367124e6") #=> #<Vault::Secret lease_id="">
96
+ #
97
+ # @param [String] wrapper
98
+ # the token to use when unwrapping the value
99
+ #
100
+ # @return [Secret, nil]
101
+ def unwrap(wrapper)
102
+ client.with_token(wrapper) do |client|
103
+ json = client.get("/v1/cubbyhole/response")
104
+ secret = Secret.decode(json)
105
+
106
+ # If there is nothing in the cubbyhole, return early.
107
+ if secret.nil? || secret.data.nil? || secret.data[:response].nil?
108
+ return nil
109
+ end
110
+
111
+ # Extract the response and parse it into a new secret.
112
+ json = JSON.parse(secret.data[:response], symbolize_names: true)
113
+ secret = Secret.decode(json)
114
+ return secret
115
+ end
116
+ rescue HTTPError => e
117
+ return nil if e.code == 404
118
+ raise
119
+ end
120
+
121
+ # Unwrap a token in a wrapped response given the temporary token.
122
+ #
123
+ # @example
124
+ # Vault.logical.unwrap("f363dba8-25a7-08c5-430c-00b2367124e6") #=> "0f0f40fd-06ce-4af1-61cb-cdc12796f42b"
125
+ #
126
+ # @param [String, Secret] wrapper
127
+ # the token to unwrap
128
+ #
129
+ # @return [String, nil]
130
+ def unwrap_token(wrapper)
131
+ # If provided a secret, grab the token. This is really just to make the
132
+ # API a bit nicer.
133
+ if wrapper.is_a?(Secret)
134
+ wrapper = wrapper.wrap_info.token
135
+ end
136
+
137
+ # Unwrap
138
+ response = unwrap(wrapper)
139
+
140
+ # If nothing was there, return nil
141
+ if response.nil? || response.auth.nil?
142
+ return nil
143
+ end
144
+
145
+ return response.auth.client_token
146
+ rescue HTTPError => e
147
+ raise
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,168 @@
1
+ require "time"
2
+
3
+ require_relative "../response"
4
+
5
+ module Vault
6
+ # Secret is a representation of a secret from Vault. Almost all data returned
7
+ # from Vault is represented as a secret.
8
+ class Secret < Response
9
+ # @!attribute [r] auth
10
+ # Authentication information for this secret, if any. Most secrets will
11
+ # contain this field, but it may also be `nil`. When authenticating to
12
+ # Vault, the resulting Vault token will be included in this embedded
13
+ # field.
14
+ #
15
+ # @example Authenticating to Vault
16
+ # secret = Vault.auth.userpass("username", "password")
17
+ # secret.auth.client_token #=> "fdb29070-6379-70c9-ca3a-46152fb66de1"
18
+ #
19
+ # @return [SecretAuth, nil]
20
+ field :auth, load: ->(v) { SecretAuth.decode(v) }
21
+
22
+ # @!attribute [r] data
23
+ # Arbitrary data returned by the secret. The keys returned are dependent
24
+ # upon the request made. For more information on the names of the keys
25
+ # that may be returned, please see the Vault documentation.
26
+ #
27
+ # @example Reading data
28
+ # secret = Vault.auth.token("abcd1234")
29
+ # secret.data[:id] #=> "abcd1234"
30
+ # secret.data[:ttl] #=> 0
31
+ #
32
+ # @return [Hash<Symbol, Object>]
33
+ field :data, freeze: true
34
+
35
+ # @!attribute [r] metadata
36
+ # Read-only metadata information related to the secret.
37
+ #
38
+ # @example Reading metadata
39
+ # secret = Vault.logical(:versioned).read("secret", "foo")
40
+ # secret.metadata[:created_time] #=> "2018-12-08T04:22:54.168065Z"
41
+ # secret.metadata[:version] #=> 1
42
+ # secret.metadata[:destroyed] #=> false
43
+ #
44
+ # @return [Hash<Symbol, Object>]
45
+ field :metadata, freeze: true
46
+
47
+ # @!attribute [r] lease_duration
48
+ # The number of seconds this lease is valid. If this number is 0 or nil,
49
+ # the secret does not expire.
50
+ #
51
+ # @example Getting lease duration
52
+ # secret = Vault.logical.read("secret/foo")
53
+ # secret.lease_duration #=> 2592000 # 30 days
54
+ #
55
+ # @return [Fixnum]
56
+ field :lease_duration
57
+
58
+ # @!attribute [r] lease_id
59
+ # Unique ID for the lease associated with this secret. The `lease_id` is a
60
+ # path and UUID that uniquely represents the secret. This may be used for
61
+ # renewing and revoking the secret, if permitted.
62
+ #
63
+ # @example Getting lease ID
64
+ # secret = Vault.logical.read("postgresql/creds/readonly")
65
+ # secret.lease_id #=> "postgresql/readonly/fdb29070-6379-70c9-ca3a-46152fb66de1"
66
+ #
67
+ # @return [String]
68
+ field :lease_id
69
+
70
+ # @!method [r] renewable?
71
+ # Returns whether this lease is renewable.
72
+ #
73
+ # @example Checking if a lease is renewable
74
+ # secret = Vault.logical.read("secret/foo")
75
+ # secret.renewable? #=> false
76
+ #
77
+ # @return [Boolean]
78
+ field :renewable, as: :renewable?
79
+
80
+ # @!attribute [r] warnings
81
+ # List of warnings returned by the Vault server. These are returned by the
82
+ # Vault server and may include deprecation information, new APIs, or
83
+ # request using the API differently in the future.
84
+ #
85
+ # @example Display warnings
86
+ # result = Vault.logical.read("secret/foo")
87
+ # result.warnings #=> ["This path has been deprecated"]
88
+ #
89
+ # @return [Array<String>, nil]
90
+ field :warnings, freeze: true
91
+
92
+ # @!attribute [r] wrap_info
93
+ # Wrapped information sent with the request (only present in Vault 0.6+).
94
+ # @return [WrapInfo, nil]
95
+ field :wrap_info, load: ->(v) { WrapInfo.decode(v) }
96
+ end
97
+
98
+ # SecretAuth is a struct that contains the information about auth data, if
99
+ # present. This is never returned alone and is usually embededded in a
100
+ # {Secret}.
101
+ class SecretAuth < Response
102
+ # @!attribute [r] accessor
103
+ # Accessor for the token. This is like a `lease_id`, but for a token.
104
+ # @return [String]
105
+ field :accessor
106
+
107
+ # @!attribute [r] client_token
108
+ # The client token for this authentication.
109
+ # @return [String]
110
+ field :client_token
111
+
112
+ # @!attribute [r] lease_duration
113
+ # Number of seconds the token is valid.
114
+ # @return [Fixnum]
115
+ field :lease_duration
116
+
117
+ # @!attribute [r] metadata
118
+ # Arbitrary metadata from the authentication.
119
+ #
120
+ # @example Listing metadata attached to an authentication
121
+ # auth.metadata #=> { :username => "sethvargo" }
122
+ #
123
+ # @return [Hash<Symbol, Object>, nil]
124
+ field :metadata, freeze: true
125
+
126
+ # @!attribute [r] policies
127
+ # List of policies attached to this authentication.
128
+ #
129
+ # @example Listing policies attached to an authentication
130
+ # auth.policies #=> ["default"]
131
+ #
132
+ # @return [Array<String>, nil]
133
+ field :policies, freeze: true
134
+
135
+ # @!attribute [r] renewable
136
+ # Returns whether this authentication is renewable.
137
+ #
138
+ # @example Checking if an authentication is renewable
139
+ # auth.renewable? #=> false
140
+ #
141
+ # @return [Boolean]
142
+ field :renewable, as: :renewable?
143
+ end
144
+
145
+ # WrapInfo is the information returned by a wrapped response. This is almost
146
+ # always embedded as part of a {Secret}.
147
+ class WrapInfo < Response
148
+ # @!attribute [r] token
149
+ # Wrapped response token. This token may be used to unwrap the response.
150
+ # @return [String]
151
+ field :token
152
+
153
+ # @!attribute [r] wrapped_accessor
154
+ # Accessor for the wrapped token. This is like a `lease_id`, but for a token.
155
+ # @return [String]
156
+ field :wrapped_accessor
157
+
158
+ # @!attribute [r] creation_time
159
+ # Date & time when the wrapped token was created
160
+ # @return [Time]
161
+ field :creation_time, load: ->(v) { Time.parse(v) }
162
+
163
+ # @!attribute [r] ttl
164
+ # The TTL on the token returned in seconds.
165
+ # @return [Fixnum]
166
+ field :ttl
167
+ end
168
+ end