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