dd-vault 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.
- checksums.yaml +7 -0
- data/.gitignore +42 -0
- data/.rspec +2 -0
- data/.travis.yml +26 -0
- data/CHANGELOG.md +228 -0
- data/Gemfile +3 -0
- data/LICENSE +362 -0
- data/README.md +214 -0
- data/Rakefile +6 -0
- data/lib/vault/api/approle.rb +218 -0
- data/lib/vault/api/auth.rb +316 -0
- data/lib/vault/api/auth_tls.rb +92 -0
- data/lib/vault/api/auth_token.rb +242 -0
- data/lib/vault/api/help.rb +33 -0
- data/lib/vault/api/logical.rb +150 -0
- data/lib/vault/api/secret.rb +156 -0
- data/lib/vault/api/sys/audit.rb +91 -0
- data/lib/vault/api/sys/auth.rb +116 -0
- data/lib/vault/api/sys/health.rb +63 -0
- data/lib/vault/api/sys/init.rb +83 -0
- data/lib/vault/api/sys/leader.rb +48 -0
- data/lib/vault/api/sys/lease.rb +49 -0
- data/lib/vault/api/sys/mount.rb +103 -0
- data/lib/vault/api/sys/policy.rb +92 -0
- data/lib/vault/api/sys/seal.rb +81 -0
- data/lib/vault/api/sys.rb +25 -0
- data/lib/vault/api.rb +12 -0
- data/lib/vault/client.rb +447 -0
- data/lib/vault/configurable.rb +48 -0
- data/lib/vault/defaults.rb +197 -0
- data/lib/vault/encode.rb +19 -0
- data/lib/vault/errors.rb +72 -0
- data/lib/vault/persistent/connection.rb +42 -0
- data/lib/vault/persistent/pool.rb +48 -0
- data/lib/vault/persistent/timed_stack_multi.rb +70 -0
- data/lib/vault/persistent.rb +1158 -0
- data/lib/vault/request.rb +43 -0
- data/lib/vault/response.rb +89 -0
- data/lib/vault/vendor/connection_pool/timed_stack.rb +178 -0
- data/lib/vault/vendor/connection_pool/version.rb +5 -0
- data/lib/vault/vendor/connection_pool.rb +150 -0
- data/lib/vault/version.rb +3 -0
- data/lib/vault.rb +49 -0
- data/vault.gemspec +30 -0
- metadata +185 -0
data/README.md
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
Vault Ruby Client [](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
|
+
if e
|
121
|
+
log "Received exception #{e} from Vault - attempt #{attempt}"
|
122
|
+
end
|
123
|
+
Vault.logical.read("secret/bacon")
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
The following options are available:
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
# :attempts - The number of retries when communicating with the Vault server.
|
131
|
+
# The default value is 2.
|
132
|
+
#
|
133
|
+
# :base - The base interval for retry exponential backoff. The default value is
|
134
|
+
# 0.05s.
|
135
|
+
#
|
136
|
+
# :max_wait - The maximum amount of time for a single exponential backoff to
|
137
|
+
# sleep. The default value is 2.0s.
|
138
|
+
|
139
|
+
Vault.with_retries(Vault::HTTPError, attempts: 5) do
|
140
|
+
# ...
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
After the number of retries have been exhausted, the original exception is raised.
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
Vault.with_retries(Exception) do
|
148
|
+
raise Exception
|
149
|
+
end #=> #<Exception>
|
150
|
+
```
|
151
|
+
|
152
|
+
#### Seal Status
|
153
|
+
```ruby
|
154
|
+
Vault.sys.seal_status
|
155
|
+
#=> #<Vault::SealStatus sealed=false, t=1, n=1, progress=0>
|
156
|
+
```
|
157
|
+
|
158
|
+
#### Create a Secret
|
159
|
+
```ruby
|
160
|
+
Vault.logical.write("secret/bacon", delicious: true, cooktime: "11")
|
161
|
+
#=> #<Vault::Secret lease_id="">
|
162
|
+
```
|
163
|
+
|
164
|
+
#### Retrieve a Secret
|
165
|
+
```ruby
|
166
|
+
Vault.logical.read("secret/bacon")
|
167
|
+
#=> #<Vault::Secret lease_id="">
|
168
|
+
```
|
169
|
+
|
170
|
+
#### Retrieve the Contents of a Secret
|
171
|
+
```ruby
|
172
|
+
secret = Vault.logical.read("secret/bacon")
|
173
|
+
secret.data #=> { :cooktime = >"11", :delicious => true }
|
174
|
+
```
|
175
|
+
|
176
|
+
### Response wrapping
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
# Request new access token as wrapped response where the TTL of the temporary
|
180
|
+
# token is "5s".
|
181
|
+
wrapped = Vault.auth_token.create(wrap_ttl: "5s")
|
182
|
+
|
183
|
+
# Unwrap the wrapped response to get the final token using the initial temporary
|
184
|
+
# token from the first request.
|
185
|
+
unwrapped = Vault.logical.unwrap(wrapped.wrap_info.token)
|
186
|
+
|
187
|
+
# Extract the final token from the response.
|
188
|
+
token = unwrapped.data.auth.client_token
|
189
|
+
```
|
190
|
+
|
191
|
+
A helper function is also provided when unwrapping a token directly:
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
# Request new access token as wrapped response where the TTL of the temporary
|
195
|
+
# token is "5s".
|
196
|
+
wrapped = Vault.auth_token.create(wrap_ttl: "5s")
|
197
|
+
|
198
|
+
# Unwrap wrapped response for final token using the initial temporary token.
|
199
|
+
token = Vault.logical.unwrap_token(wrapped)
|
200
|
+
```
|
201
|
+
|
202
|
+
|
203
|
+
Development
|
204
|
+
-----------
|
205
|
+
1. Clone the project on GitHub
|
206
|
+
2. Create a feature branch
|
207
|
+
3. Submit a Pull Request
|
208
|
+
|
209
|
+
Important Notes:
|
210
|
+
|
211
|
+
- **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.
|
212
|
+
- **The tests must be be idempotent.** The HTTP calls made during a test should be able to be run over and over.
|
213
|
+
- **Tests are order independent.** The default RSpec configuration randomizes the test order, so this should not be a problem.
|
214
|
+
- **Integration tests require Vault** Vault must be available in the path for the integration tests to pass.
|
data/Rakefile
ADDED
@@ -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
|