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
@@ -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