cerberus_client 1.5.1 → 2.0.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 +4 -4
- data/README.md +10 -6
- data/cerberus_client.gemspec +5 -5
- data/lib/cerberus/assumed_role_credentials_provider_chain.rb +8 -8
- data/lib/cerberus/aws_assumed_role_credentials_provider.rb +144 -0
- data/lib/cerberus/aws_principal_credentials_provider.rb +96 -73
- data/lib/cerberus/cerberus_auth_info.rb +13 -0
- data/lib/cerberus/cerberus_client.rb +138 -0
- data/lib/cerberus/cerberus_client_token.rb +3 -3
- data/lib/cerberus/default_credentials_provider_chain.rb +10 -9
- data/lib/cerberus/default_url_resolver.rb +7 -7
- data/lib/cerberus/env_credentials_provider.rb +5 -5
- data/lib/cerberus_client.rb +27 -37
- data/lib/{cerberus_client → cerberus_utils}/default_logger.rb +1 -1
- data/lib/{cerberus_client → cerberus_utils}/http.rb +10 -9
- data/lib/{cerberus_client → cerberus_utils}/log.rb +1 -1
- data/lib/cerberus_utils/utils.rb +24 -0
- data/lib/cerberus_utils/version.rb +3 -0
- metadata +13 -11
- data/lib/cerberus/aws_role_credentials_provider.rb +0 -239
- data/lib/cerberus/aws_role_info.rb +0 -15
- data/lib/cerberus/vault_client.rb +0 -207
- data/lib/cerberus_client/version.rb +0 -3
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cerberus_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
+
- Shaun Ford
|
7
8
|
- Joe Teibel
|
8
9
|
autorequire:
|
9
10
|
bindir: exe
|
10
11
|
cert_chain: []
|
11
|
-
date: 2017-
|
12
|
+
date: 2017-10-17 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: aws-sdk
|
@@ -52,13 +53,13 @@ dependencies:
|
|
52
53
|
- - ~>
|
53
54
|
- !ruby/object:Gem::Version
|
54
55
|
version: '1.13'
|
55
|
-
description: This is a Ruby based client library for communicating with
|
56
|
+
description: This is a Ruby based client library for communicating with Cerberus via
|
56
57
|
HTTP and enables authentication schemes specific to AWS and Cerberus. This client
|
57
58
|
currently supports read-only operations (write operations are not yet implemented,
|
58
59
|
feel free to open a pull request to implement write operations). To learn more about
|
59
60
|
Cerberus, please visit the Cerberus website.
|
60
61
|
email:
|
61
|
-
-
|
62
|
+
- shaun.ford@nike.com
|
62
63
|
executables: []
|
63
64
|
extensions: []
|
64
65
|
extra_rdoc_files: []
|
@@ -73,9 +74,10 @@ files:
|
|
73
74
|
- README.md
|
74
75
|
- cerberus_client.gemspec
|
75
76
|
- lib/cerberus/assumed_role_credentials_provider_chain.rb
|
77
|
+
- lib/cerberus/aws_assumed_role_credentials_provider.rb
|
76
78
|
- lib/cerberus/aws_principal_credentials_provider.rb
|
77
|
-
- lib/cerberus/
|
78
|
-
- lib/cerberus/
|
79
|
+
- lib/cerberus/cerberus_auth_info.rb
|
80
|
+
- lib/cerberus/cerberus_client.rb
|
79
81
|
- lib/cerberus/cerberus_client_token.rb
|
80
82
|
- lib/cerberus/default_credentials_provider_chain.rb
|
81
83
|
- lib/cerberus/default_url_resolver.rb
|
@@ -84,12 +86,12 @@ files:
|
|
84
86
|
- lib/cerberus/exception/http_error.rb
|
85
87
|
- lib/cerberus/exception/no_valid_providers.rb
|
86
88
|
- lib/cerberus/exception/no_value_error.rb
|
87
|
-
- lib/cerberus/vault_client.rb
|
88
89
|
- lib/cerberus_client.rb
|
89
|
-
- lib/
|
90
|
-
- lib/
|
91
|
-
- lib/
|
92
|
-
- lib/
|
90
|
+
- lib/cerberus_utils/default_logger.rb
|
91
|
+
- lib/cerberus_utils/http.rb
|
92
|
+
- lib/cerberus_utils/log.rb
|
93
|
+
- lib/cerberus_utils/utils.rb
|
94
|
+
- lib/cerberus_utils/version.rb
|
93
95
|
homepage: https://github.com/Nike-Inc/cerberus-ruby-client
|
94
96
|
licenses:
|
95
97
|
- Apache License Version 2
|
@@ -1,239 +0,0 @@
|
|
1
|
-
module Cerberus
|
2
|
-
|
3
|
-
require_relative('../cerberus_client/log')
|
4
|
-
require_relative('../cerberus_client/http')
|
5
|
-
require_relative('../cerberus_client')
|
6
|
-
require_relative('exception/no_value_error')
|
7
|
-
require_relative('exception/http_error')
|
8
|
-
require_relative('cerberus_client_token')
|
9
|
-
require_relative('aws_role_info')
|
10
|
-
require('aws-sdk')
|
11
|
-
require('net/http')
|
12
|
-
require('json')
|
13
|
-
require('base64')
|
14
|
-
|
15
|
-
##
|
16
|
-
# The AWS IAM role credentials provider
|
17
|
-
##
|
18
|
-
class AwsRoleCredentialsProvider
|
19
|
-
|
20
|
-
# AWS metadata instance URL
|
21
|
-
INSTANCE_METADATA_SVC_BASE_URL = "http://169.254.169.254/latest/meta-data"
|
22
|
-
# relative URI to look up AZ in AWS metadata svc
|
23
|
-
REGION_REL_URI = "/placement/availability-zone"
|
24
|
-
# relative URI to look up IAM role in AWS metadata svc
|
25
|
-
IAM_ROLE_INFO_REL_URI = "/iam/info"
|
26
|
-
# reference into the metadata data json we get to look up IAM role
|
27
|
-
IAM_ROLE_ARN_KEY = 'InstanceProfileArn'
|
28
|
-
# magic number is the index into a split role ARN to grab the acccount ID
|
29
|
-
ROLE_ARN_ARRAY_INDEX_OF_ACCOUNTNUM = 4
|
30
|
-
# magic number is the index into a split role ARN to grab the role name
|
31
|
-
ROLE_ARN_ARRAY_INDEX_OF_ROLENAME = 1
|
32
|
-
# relative URI to get encrypted auth data from Cerberus
|
33
|
-
ROLE_AUTH_REL_URI = "/v1/auth/iam-role"
|
34
|
-
# reference into the decrypted auth data json we get from Cerberus
|
35
|
-
CERBERUS_AUTH_DATA_CLIENT_TOKEN_KEY = "client_token"
|
36
|
-
CERBERUS_AUTH_DATA_LEASE_DURATION_KEY = "lease_duration"
|
37
|
-
CERBERUS_AUTH_DATA_POLICIES_KEY = "policies"
|
38
|
-
|
39
|
-
LOGGER = CerberusClient::Log.instance
|
40
|
-
|
41
|
-
##
|
42
|
-
# Init AWS role provider - needs vault base url. Instance metadata service url is optional to make unit tests
|
43
|
-
# easier and so we can provide a hook to set this via config as needed
|
44
|
-
##
|
45
|
-
def initialize(vaultBaseUrl, instanceMdSvcBaseUrl = nil, roleName = nil, roleRegion = nil, roleAccountId = nil)
|
46
|
-
@vaultBaseUrl = vaultBaseUrl
|
47
|
-
@clientToken = nil
|
48
|
-
@role = get_role_info(instanceMdSvcBaseUrl, roleName, roleRegion, roleAccountId)
|
49
|
-
|
50
|
-
LOGGER.debug("AwsRoleCredentialsProvider initialized with vault base url #{@vaultBaseUrl}")
|
51
|
-
end
|
52
|
-
|
53
|
-
##
|
54
|
-
# Get credentials using AWS IAM role
|
55
|
-
##
|
56
|
-
def getClientToken
|
57
|
-
|
58
|
-
if (@role.nil?)
|
59
|
-
raise Cerberus::Exception::NoValueError
|
60
|
-
end
|
61
|
-
|
62
|
-
if (@clientToken.nil?)
|
63
|
-
@clientToken = getCredentialsFromCerberus
|
64
|
-
end
|
65
|
-
|
66
|
-
# using two if statements here just to make the logging easier..
|
67
|
-
# the above we expect on startup, expiration is an interesting event worth a debug log all its own
|
68
|
-
if (@clientToken.expired?)
|
69
|
-
LOGGER.debug("Existing ClientToken has expired - refreshing from Cerberus...")
|
70
|
-
@clientToken = getCredentialsFromCerberus
|
71
|
-
end
|
72
|
-
|
73
|
-
return @clientToken.authToken
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
##
|
78
|
-
# Policy: if we are given these three pieces of data, we will assume a role to do auth
|
79
|
-
##
|
80
|
-
def should_assume_role?(roleAccountId, roleName, roleRegion)
|
81
|
-
!(roleName.nil? || roleAccountId.nil? || roleRegion.nil?)
|
82
|
-
end
|
83
|
-
|
84
|
-
##
|
85
|
-
# Policy: if we do not have an instance MD service URL and we can't assume a role, then this instance
|
86
|
-
# of the provider cannot use a role to provide credentials. Primarily used for testing.
|
87
|
-
##
|
88
|
-
def have_access_to_role?(instanceMdSvcBaseUrl, roleName, roleRegion, roleAccountId)
|
89
|
-
(!instanceMdSvcBaseUrl.nil? || should_assume_role?(roleName, roleRegion, roleAccountId))
|
90
|
-
end
|
91
|
-
|
92
|
-
private
|
93
|
-
|
94
|
-
##
|
95
|
-
# Uses provided data to determine how to construct the AwsRoleInfo use by this provider
|
96
|
-
##
|
97
|
-
def get_role_info(instanceMdSvcBaseUrl, roleName, roleRegion, roleAccountId)
|
98
|
-
|
99
|
-
# if we have no metedata about how to auth, we do nothing
|
100
|
-
# this is used in unit testing primarily
|
101
|
-
if (!have_access_to_role?(instanceMdSvcBaseUrl, roleName, roleRegion, roleAccountId))
|
102
|
-
return nil;
|
103
|
-
elsif (should_assume_role?(roleAccountId, roleName, roleRegion))
|
104
|
-
# we are assuming a role to do auth
|
105
|
-
return get_role_from_provided_info(roleName, roleRegion, roleAccountId)
|
106
|
-
else
|
107
|
-
# we are using a role that the instance has associated with it
|
108
|
-
@instanceMdSvcBaseUrl = instanceMdSvcBaseUrl.nil? ? INSTANCE_METADATA_SVC_BASE_URL : instanceMdSvcBaseUrl
|
109
|
-
|
110
|
-
# collect instance MD we need to auth with Cerberus
|
111
|
-
return get_role_from_instance_metadata
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
|
116
|
-
##
|
117
|
-
# Get an AwsRoleInfo object from the provided data
|
118
|
-
##
|
119
|
-
def get_role_from_provided_info(roleName, roleRegion, roleAccountId)
|
120
|
-
|
121
|
-
role_creds = Aws::AssumeRoleCredentials.new(
|
122
|
-
client: Aws::STS::Client.new(region: roleRegion),
|
123
|
-
role_arn: "arn:aws:iam::#{roleAccountId}:role/#{roleName}",
|
124
|
-
role_session_name: "hiera-cpe-build")
|
125
|
-
|
126
|
-
return AwsRoleInfo.new(roleName, roleRegion, roleAccountId, credentials: role_creds)
|
127
|
-
end
|
128
|
-
|
129
|
-
##
|
130
|
-
# Use the instance metadata to extract the role information
|
131
|
-
# This function should only be called from an EC2 instance otherwise the http
|
132
|
-
# call will fail.
|
133
|
-
##
|
134
|
-
def get_role_from_instance_metadata
|
135
|
-
role_arn = getIAMRoleARN
|
136
|
-
region = getRegionFromAZ(getAvailabilityZone)
|
137
|
-
account_id = getAccountIdFromRoleARN(role_arn)
|
138
|
-
role_name = getRoleNameFromRoleARN(role_arn)
|
139
|
-
|
140
|
-
LOGGER.debug("roleARN #{role_arn}")
|
141
|
-
LOGGER.debug("region #{region}")
|
142
|
-
LOGGER.debug("accountId #{account_id}")
|
143
|
-
LOGGER.debug("roleName #{role_name}")
|
144
|
-
|
145
|
-
return AwsRoleInfo.new(role_name, region, account_id, nil)
|
146
|
-
end
|
147
|
-
|
148
|
-
##
|
149
|
-
# Reach out to the Cerberus management service and get an auth token
|
150
|
-
##
|
151
|
-
def getCredentialsFromCerberus
|
152
|
-
begin
|
153
|
-
authData = doAuthWithCerberus(@role.account_id, @role.name, @role.region)
|
154
|
-
|
155
|
-
LOGGER.debug("Got auth data from Cerberus. Attempting to decrypt...")
|
156
|
-
|
157
|
-
# decrypt the data we got from cerberus to get the vault token
|
158
|
-
kms = Aws::KMS::Client.new(region: @role.region, credentials: @role.credentials[:credentials])
|
159
|
-
|
160
|
-
decryptedAuthDataJson = JSON.parse(kms.decrypt(ciphertext_blob: Base64.decode64(authData)).plaintext)
|
161
|
-
|
162
|
-
LOGGER.debug("Decrypt successful. Passing back Cerberus auth token.")
|
163
|
-
# pass back a credentials object that will allow us to reuse it until it expires
|
164
|
-
CerberusClientToken.new(decryptedAuthDataJson[CERBERUS_AUTH_DATA_CLIENT_TOKEN_KEY],
|
165
|
-
decryptedAuthDataJson[CERBERUS_AUTH_DATA_LEASE_DURATION_KEY],
|
166
|
-
decryptedAuthDataJson[CERBERUS_AUTH_DATA_POLICIES_KEY])
|
167
|
-
|
168
|
-
rescue Cerberus::Exception::HttpError
|
169
|
-
# catch http errors here and assert no value
|
170
|
-
# this may not actually be the case, there are legitimate reasons HTTP can fail when it "should" work
|
171
|
-
# but this is handled by logging - a warning is set in the log in during the HTTP call
|
172
|
-
raise Cerberus::Exception::NoValueError
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
##
|
177
|
-
# Get the AWS account ID from the role ARN
|
178
|
-
# Expects formatting [some value]:[some value]:[some value]::[account id]
|
179
|
-
##
|
180
|
-
def getAccountIdFromRoleARN(roleARN)
|
181
|
-
roleARN.split(':')[ROLE_ARN_ARRAY_INDEX_OF_ACCOUNTNUM]
|
182
|
-
end
|
183
|
-
|
184
|
-
##
|
185
|
-
# Get the role name from the role ARN
|
186
|
-
# Expects formatting [some value]:[some value]:[some value]::[account id]
|
187
|
-
##
|
188
|
-
def getRoleNameFromRoleARN(roleARN)
|
189
|
-
roleARN.split('/')[ROLE_ARN_ARRAY_INDEX_OF_ROLENAME]
|
190
|
-
end
|
191
|
-
|
192
|
-
##
|
193
|
-
# Read the IAM role ARN from the instance metadata
|
194
|
-
# Will throw an HTTP exception if there is no IAM role associated with the instance
|
195
|
-
##
|
196
|
-
def getIAMRoleARN
|
197
|
-
response = doHttpToMDService(IAM_ROLE_INFO_REL_URI)
|
198
|
-
jsonResponseBody = JSON.parse(response.body)
|
199
|
-
jsonResponseBody[IAM_ROLE_ARN_KEY]
|
200
|
-
end
|
201
|
-
|
202
|
-
##
|
203
|
-
# Get the region from AWS instance metadata
|
204
|
-
##
|
205
|
-
def getAvailabilityZone
|
206
|
-
doHttpToMDService(REGION_REL_URI).body
|
207
|
-
end
|
208
|
-
|
209
|
-
##
|
210
|
-
# Get region from AZ
|
211
|
-
##
|
212
|
-
def getRegionFromAZ(az)
|
213
|
-
az[0, az.length-1]
|
214
|
-
end
|
215
|
-
|
216
|
-
##
|
217
|
-
# Call the instance metadata service with a relative URI and return the response if the call succeeds
|
218
|
-
# else throw an IOError for non-2xx responses and RuntimeError for any exceptions down the stack
|
219
|
-
##
|
220
|
-
def doHttpToMDService(relUri)
|
221
|
-
url = URI(@instanceMdSvcBaseUrl + relUri)
|
222
|
-
CerberusClient::Http.new.doHttp(url, 'GET', false)
|
223
|
-
end
|
224
|
-
|
225
|
-
##
|
226
|
-
#
|
227
|
-
##
|
228
|
-
def doAuthWithCerberus(accountId, roleName, region)
|
229
|
-
postJsonData = JSON.generate({:account_id => accountId, :role_name => roleName, :region => region})
|
230
|
-
authUrl = URI(@vaultBaseUrl + ROLE_AUTH_REL_URI)
|
231
|
-
useSSL = ! ("#{@vaultBaseUrl}".include? "localhost")
|
232
|
-
authResponse = CerberusClient::Http.new.doHttp(authUrl, 'POST', useSSL, postJsonData)
|
233
|
-
# if we got this far, we should have a valid response with encrypted data
|
234
|
-
# send back the encrypted data
|
235
|
-
JSON.parse(authResponse.body)['auth_data']
|
236
|
-
end
|
237
|
-
|
238
|
-
end
|
239
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
module Cerberus
|
2
|
-
class AwsRoleInfo
|
3
|
-
attr_reader :name
|
4
|
-
attr_reader :region
|
5
|
-
attr_reader :credentials
|
6
|
-
attr_reader :account_id
|
7
|
-
|
8
|
-
def initialize(name, region, account_id, credentials = nil)
|
9
|
-
@name = name
|
10
|
-
@region = region
|
11
|
-
@account_id = account_id
|
12
|
-
@credentials = credentials
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,207 +0,0 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module Cerberus
|
4
|
-
|
5
|
-
require_relative('../cerberus_client/log')
|
6
|
-
require_relative('exception/http_error')
|
7
|
-
require_relative('exception/ambiguous_vault_bad_request')
|
8
|
-
require_relative('default_credentials_provider_chain')
|
9
|
-
require('json')
|
10
|
-
|
11
|
-
##
|
12
|
-
# Client for interacting with the Vault API
|
13
|
-
##
|
14
|
-
class VaultClient
|
15
|
-
|
16
|
-
# relative path to the Vault secrets API
|
17
|
-
SECRET_PATH_PREFIX = "/v1/secret/"
|
18
|
-
SECRET_MAP_DATA_KEY = VAULT_LIST_DATA_KEY = "data"
|
19
|
-
VAULT_TOKEN_HEADER_KEY = 'X-Vault-Token'
|
20
|
-
VAULT_ERRORS_KEY = "errors"
|
21
|
-
VAULT_PERMISSION_DENIED_ERR = "permission denied"
|
22
|
-
VAULT_LIST_KEYS_KEY = "keys"
|
23
|
-
VAULT_LIST_PARAM_KEY = "list"
|
24
|
-
SLASH = "/"
|
25
|
-
|
26
|
-
attr_reader :vaultBaseUrl
|
27
|
-
attr_reader :credentialsProvider
|
28
|
-
|
29
|
-
##
|
30
|
-
# Init with the base URL for vault
|
31
|
-
##
|
32
|
-
def initialize(urlResolver, credentialsProviderChain)
|
33
|
-
|
34
|
-
require 'net/https'
|
35
|
-
|
36
|
-
@vaultBaseUrl = CerberusClient.getUrlFromResolver(urlResolver)
|
37
|
-
@credentialsProvider = credentialsProviderChain.getCredentialsProvider
|
38
|
-
|
39
|
-
end # initialize
|
40
|
-
|
41
|
-
##
|
42
|
-
# Read operation for a specified path.
|
43
|
-
##
|
44
|
-
def read(path)
|
45
|
-
begin
|
46
|
-
response = doVaultHttpGet(SECRET_PATH_PREFIX + path)
|
47
|
-
CerberusClient::Log.instance.debug("VaultClient::read(path) HTTP response: #{response.code}, #{response.message}")
|
48
|
-
response.body
|
49
|
-
|
50
|
-
rescue => ex
|
51
|
-
CerberusClient::Log.instance.error("VaultClient::read(#{path}) unhandled exception trying to read: #{ex.message}")
|
52
|
-
raise ex
|
53
|
-
end
|
54
|
-
end # read
|
55
|
-
|
56
|
-
##
|
57
|
-
# Read operation for a specified path.
|
58
|
-
##
|
59
|
-
def readKey(path, key)
|
60
|
-
begin
|
61
|
-
CerberusClient::Log.instance.error("VaultClient::read(#{path}, #{key})")
|
62
|
-
|
63
|
-
readPathAndIterateOnDataWithProc(path, &(Proc.new { |k, v| if(key == k); return v; end }) )
|
64
|
-
|
65
|
-
# else, we didn't find it
|
66
|
-
return nil
|
67
|
-
|
68
|
-
rescue => ex
|
69
|
-
CerberusClient::Log.instance.error("VaultClient::read(#{path}, #{key}) unhandled exception trying to read: #{ex.message}")
|
70
|
-
raise ex
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
##
|
75
|
-
# Returns a list of key names at the specified location. Folders are suffixed with /.
|
76
|
-
# The input must be a folder; list on a file will return nil
|
77
|
-
##
|
78
|
-
def list(path)
|
79
|
-
begin
|
80
|
-
response = doVaultHttpGet(SECRET_PATH_PREFIX + path + "?list=true")
|
81
|
-
|
82
|
-
CerberusClient::Log.instance.debug("VaultClient::list(#{path}) HTTP response: #{response.code}, #{response.message}")
|
83
|
-
|
84
|
-
jsonResonseBody = JSON.parse(response.body)
|
85
|
-
pathList = jsonResonseBody[VAULT_LIST_DATA_KEY][VAULT_LIST_KEYS_KEY]
|
86
|
-
CerberusClient::Log.instance.debug("VaultClient::list returning #{pathList.join(", ")} ")
|
87
|
-
pathList
|
88
|
-
|
89
|
-
rescue => ex
|
90
|
-
|
91
|
-
# check to see if we threw the Http error with a response object
|
92
|
-
response = (ex.instance_of?(Cerberus::Exception::HttpError)) ? ex.response : nil
|
93
|
-
if(!response.nil? && response.code.to_i == 404)
|
94
|
-
return nil
|
95
|
-
end
|
96
|
-
|
97
|
-
CerberusClient::Log.instance.error("VaultClient::list(#{path}) unhandled exception trying to read: #{ex.message}")
|
98
|
-
raise ex
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
##
|
103
|
-
# This is potentially an expensive operation depending on the depth of the tree we're trying to parse
|
104
|
-
# It recursively walks 'path' and returns a hash of all child [path] => [array of keys] found under 'path'
|
105
|
-
# if 'path' is a folder, it must have a trailing slash ('/'). If 'path' is an "end node" or "vault file", then it
|
106
|
-
# should not have a trailing slash ('/')
|
107
|
-
##
|
108
|
-
def describe!(path, resultHash = nil)
|
109
|
-
|
110
|
-
CerberusClient::Log.instance.debug("VaultClient::describe!(#{path})")
|
111
|
-
|
112
|
-
if(resultHash == nil)
|
113
|
-
resultHash = Hash.new()
|
114
|
-
end
|
115
|
-
|
116
|
-
curChildren = list(path)
|
117
|
-
|
118
|
-
# if curChildren is nil, it's possible there are no children or that we don't have access
|
119
|
-
# It's also possible it is the "end" of the path... what Vault calls "the file"
|
120
|
-
# in that case, we should send back the keys in that path so give it a shot
|
121
|
-
if(curChildren.nil?)
|
122
|
-
resultHash[path] = Array.new
|
123
|
-
readPathAndIterateOnDataWithProc(path, &(Proc.new { |key, value| resultHash[path] << key }) )
|
124
|
-
return resultHash
|
125
|
-
end
|
126
|
-
|
127
|
-
curChildren.each { |childNode|
|
128
|
-
curLocation = path + childNode
|
129
|
-
# if childNode ends with '/' then we have a directory we need to call into
|
130
|
-
if(childNode.end_with?(SLASH))
|
131
|
-
describe!(curLocation, resultHash)
|
132
|
-
else # it is a "directory" that contains keys
|
133
|
-
resultHash[curLocation] = Array.new
|
134
|
-
readPathAndIterateOnDataWithProc(curLocation, &(Proc.new { |key, value| resultHash[curLocation] << key }) )
|
135
|
-
end
|
136
|
-
}
|
137
|
-
return resultHash
|
138
|
-
end
|
139
|
-
|
140
|
-
private
|
141
|
-
|
142
|
-
|
143
|
-
##
|
144
|
-
# Attempts to execute the proc passed in on every key, value located in the 'data' element read at 'path'
|
145
|
-
##
|
146
|
-
def readPathAndIterateOnDataWithProc(path, &p)
|
147
|
-
jsonResponseBody = JSON.parse(read(path))
|
148
|
-
jsonResponseBody[VAULT_LIST_DATA_KEY].each { |dataMapKey, dataMapValue|
|
149
|
-
p.call(dataMapKey, dataMapValue)
|
150
|
-
}
|
151
|
-
end
|
152
|
-
|
153
|
-
##
|
154
|
-
# Do an http request to Vault using the relative URI passed in
|
155
|
-
##
|
156
|
-
def doVaultHttpGet(relativeUri)
|
157
|
-
|
158
|
-
url = URI(@vaultBaseUrl + relativeUri)
|
159
|
-
useSSL = ! ("#{@vaultBaseUrl}".include? "localhost")
|
160
|
-
|
161
|
-
begin
|
162
|
-
response = CerberusClient::Http.new.doHttp(url,
|
163
|
-
'GET', useSSL, nil,
|
164
|
-
{VAULT_TOKEN_HEADER_KEY =>
|
165
|
-
CerberusClient.getCredentialsFromProvider(@credentialsProvider)})
|
166
|
-
|
167
|
-
rescue Cerberus::Exception::HttpError => ex
|
168
|
-
# Since Vault wants to pass back 400 bad request for both paths we don't have access to
|
169
|
-
# and paths that don't actually exist at all, I'm sending back a specific error so that implementing clients
|
170
|
-
# can at least understand the situation they find themselves in
|
171
|
-
#
|
172
|
-
# This client could actually work around this problem by first getting a list of all paths we have access to and
|
173
|
-
# determining if the path exists in that list. If not, 404 (which is more appropriate than 400).
|
174
|
-
# TODO: implement "list > check for path" work around <-- NOTE: This is a relatively expensive operation
|
175
|
-
|
176
|
-
if(!ex.response.nil? && (ex.response.code.to_i == 400) && (hasPermissionErrors?(ex.response.body)))
|
177
|
-
raise Exception::AmbiguousVaultBadRequest.new
|
178
|
-
else
|
179
|
-
raise ex
|
180
|
-
end
|
181
|
-
|
182
|
-
end
|
183
|
-
|
184
|
-
response
|
185
|
-
|
186
|
-
end # doVaultHttp
|
187
|
-
|
188
|
-
##
|
189
|
-
# Parse out the permission errors json
|
190
|
-
##
|
191
|
-
def hasPermissionErrors?(jsonErrorsMsg)
|
192
|
-
begin
|
193
|
-
json = JSON.parse(jsonErrorsMsg)
|
194
|
-
json[VAULT_ERRORS_KEY].each { |err|
|
195
|
-
if(err == VAULT_PERMISSION_DENIED_ERR)
|
196
|
-
return true
|
197
|
-
end
|
198
|
-
}
|
199
|
-
rescue => ex
|
200
|
-
CerberusClient::Log.instance.warn(
|
201
|
-
"VaultClient::hasPermissionErrors? called and exception thrown parsing #{jsonErrorsMsg}: #{ex.message}")
|
202
|
-
return false
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
end
|
207
|
-
end
|