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
@@ -0,0 +1,13 @@
|
|
1
|
+
module Cerberus
|
2
|
+
class CerberusAuthInfo
|
3
|
+
attr_reader :iam_principal_arn
|
4
|
+
attr_reader :region
|
5
|
+
attr_reader :credentials
|
6
|
+
|
7
|
+
def initialize(iam_principal_arn, region, credentials = nil)
|
8
|
+
@iam_principal_arn = iam_principal_arn
|
9
|
+
@region = region
|
10
|
+
@credentials = credentials
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Cerberus
|
4
|
+
|
5
|
+
require_relative('../cerberus_utils/log')
|
6
|
+
require_relative('../cerberus_utils/utils')
|
7
|
+
require_relative('exception/http_error')
|
8
|
+
require_relative('exception/ambiguous_vault_bad_request')
|
9
|
+
require_relative('default_credentials_provider_chain')
|
10
|
+
require('json')
|
11
|
+
|
12
|
+
##
|
13
|
+
# Client for interacting with the Cerberus API
|
14
|
+
##
|
15
|
+
class CerberusClient
|
16
|
+
|
17
|
+
# relative path to the Cerberus secrets API
|
18
|
+
SECRET_PATH_PREFIX = "/v1/secret/"
|
19
|
+
SECRET_MAP_DATA_KEY = CERBERUS_LIST_DATA_KEY = "data"
|
20
|
+
CERBERUS_TOKEN_HEADER_KEY = 'X-Vault-Token'
|
21
|
+
CERBERUS_ERRORS_KEY = "errors"
|
22
|
+
CERBERUS_PERMISSION_DENIED_ERR = "permission denied"
|
23
|
+
CERBERUS_LIST_KEYS_KEY = "keys"
|
24
|
+
CERBERUS_LIST_PARAM_KEY = "list"
|
25
|
+
SLASH = "/"
|
26
|
+
|
27
|
+
attr_reader :cerberus_base_url
|
28
|
+
attr_reader :credentials_provider
|
29
|
+
|
30
|
+
##
|
31
|
+
# Init with the base URL for cerberus
|
32
|
+
##
|
33
|
+
def initialize(cerberus_url_resolver, credentials_provider_chain)
|
34
|
+
|
35
|
+
require 'net/https'
|
36
|
+
|
37
|
+
@cerberus_base_url = CerberusUtils::get_url_from_resolver(cerberus_url_resolver)
|
38
|
+
@credentials_provider = credentials_provider_chain.get_credentials_provider
|
39
|
+
|
40
|
+
end # initialize
|
41
|
+
|
42
|
+
##
|
43
|
+
# Read operation for a specified path.
|
44
|
+
##
|
45
|
+
def read(path)
|
46
|
+
begin
|
47
|
+
response = read_value_from_cerberus(SECRET_PATH_PREFIX + path)
|
48
|
+
CerberusUtils::Log.instance.debug("CerberusClient::read(path) HTTP response: #{response.code}, #{response.message}")
|
49
|
+
response.body
|
50
|
+
|
51
|
+
rescue => ex
|
52
|
+
CerberusUtils::Log.instance.error("CerberusClient::read(#{path}) unhandled exception trying to read: #{ex.message}")
|
53
|
+
raise ex
|
54
|
+
end
|
55
|
+
end # read
|
56
|
+
|
57
|
+
##
|
58
|
+
# Returns a list of key names at the specified location. Folders are suffixed with /.
|
59
|
+
# The input must be a folder; list on a file will return nil
|
60
|
+
##
|
61
|
+
def list(path)
|
62
|
+
begin
|
63
|
+
response = read_value_from_cerberus(SECRET_PATH_PREFIX + path + "?list=true")
|
64
|
+
|
65
|
+
CerberusUtils::Log.instance.debug("CerberusClient::list(#{path}) HTTP response: #{response.code}, #{response.message}")
|
66
|
+
|
67
|
+
json_response_body = JSON.parse(response.body)
|
68
|
+
pathList = json_response_body[CERBERUS_LIST_DATA_KEY][CERBERUS_LIST_KEYS_KEY]
|
69
|
+
CerberusUtils::Log.instance.debug("CerberusClient::list returning #{pathList.join(", ")} ")
|
70
|
+
pathList
|
71
|
+
|
72
|
+
rescue => ex
|
73
|
+
|
74
|
+
# check to see if we threw the Http error with a response object
|
75
|
+
response = (ex.instance_of?(Cerberus::Exception::HttpError)) ? ex.response : nil
|
76
|
+
if(!response.nil? && response.code.to_i == 404)
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
|
80
|
+
CerberusUtils::Log.instance.error("CerberusClient::list(#{path}) unhandled exception trying to read: #{ex.message}")
|
81
|
+
raise ex
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
##
|
88
|
+
# Do an http request to Cerberus using the relative URI passed in
|
89
|
+
##
|
90
|
+
def read_value_from_cerberus(releative_uri)
|
91
|
+
|
92
|
+
url = URI(@cerberus_base_url + releative_uri)
|
93
|
+
use_ssl, = ! ("#{@cerberus_base_url}".include? "localhost")
|
94
|
+
|
95
|
+
begin
|
96
|
+
headers_map = {CERBERUS_TOKEN_HEADER_KEY => @credentials_provider.get_client_token}
|
97
|
+
response = CerberusUtils::Http.new.make_http_call(url, "GET", use_ssl, nil, headers_map)
|
98
|
+
|
99
|
+
rescue Cerberus::Exception::HttpError => ex
|
100
|
+
# Since Vault wants to pass back 400 bad request for both paths we don't have access to
|
101
|
+
# and paths that don't actually exist at all, I'm sending back a specific error so that implementing clients
|
102
|
+
# can at least understand the situation they find themselves in
|
103
|
+
#
|
104
|
+
# This client could actually work around this problem by first getting a list of all paths we have access to and
|
105
|
+
# determining if the path exists in that list. If not, 404 (which is more appropriate than 400).
|
106
|
+
# TODO: implement "list > check for path" work around <-- NOTE: This is a relatively expensive operation
|
107
|
+
if(!ex.response.nil? && (ex.response.code.to_i == 400))
|
108
|
+
raise Exception::AmbiguousVaultBadRequest.new
|
109
|
+
else
|
110
|
+
raise ex
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
response
|
116
|
+
|
117
|
+
end # read_value_from_cerberus
|
118
|
+
|
119
|
+
##
|
120
|
+
# Parse out the permission errors json
|
121
|
+
##
|
122
|
+
def has_permission_errors?(json_errors_msg)
|
123
|
+
begin
|
124
|
+
json = JSON.parse(json_errors_msg)
|
125
|
+
json[CERBERUS_ERRORS_KEY].each { |err|
|
126
|
+
if(err == CERBERUS_PERMISSION_DENIED_ERR)
|
127
|
+
return true
|
128
|
+
end
|
129
|
+
}
|
130
|
+
rescue => ex
|
131
|
+
CerberusUtils::Log.instance.warn(
|
132
|
+
"CerberusClient::hasPermissionErrors? called and exception thrown parsing #{json_errors_msg}: #{ex.message}")
|
133
|
+
return false
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Cerberus
|
4
4
|
|
5
|
-
require_relative('../
|
5
|
+
require_relative('../cerberus_utils/log')
|
6
6
|
|
7
7
|
##
|
8
8
|
# Object to hold the Cerberus client token credentials and check for expiration and refresh
|
@@ -20,8 +20,8 @@ module Cerberus
|
|
20
20
|
@createTime = Time.now
|
21
21
|
@cacheLifetimeSec = cacheLifetimeSec
|
22
22
|
@policies = policiesArray
|
23
|
-
|
24
|
-
|
23
|
+
CerberusUtils::Log.instance.debug("AwsCredentials cache lifetime set to #{@cacheLifetimeSec} seconds")
|
24
|
+
CerberusUtils::Log.instance.debug("AwsCredentials policies: #{@policies.join(", ")}")
|
25
25
|
@authToken = authToken
|
26
26
|
end
|
27
27
|
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require_relative('../cerberus_client')
|
2
|
-
require_relative('../
|
2
|
+
require_relative('../cerberus_utils/log')
|
3
|
+
require_relative('../cerberus_utils/utils')
|
3
4
|
require_relative('exception/no_value_error')
|
4
5
|
require_relative('exception/no_valid_providers')
|
5
|
-
require_relative('aws_role_credentials_provider')
|
6
6
|
require_relative('aws_principal_credentials_provider')
|
7
7
|
require_relative('env_credentials_provider')
|
8
8
|
|
@@ -13,24 +13,25 @@ module Cerberus
|
|
13
13
|
##
|
14
14
|
class DefaultCredentialsProviderChain
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
# AWS metadata instance URL
|
17
|
+
AWS_EC2_METADATA_URL = "http://169.254.169.254/latest/meta-data"
|
18
|
+
|
19
|
+
def initialize(url_resolver, region = nil, instance_metadata_url = AWS_EC2_METADATA_URL)
|
18
20
|
|
19
21
|
# return default array of providers
|
20
22
|
@providers = [Cerberus::EnvCredentialsProvider.new,
|
21
|
-
Cerberus::
|
22
|
-
Cerberus::AwsPrincipalCredentialsProvider.new(vaultBaseUrl)]
|
23
|
+
Cerberus::AwsPrincipalCredentialsProvider.new(url_resolver, region, instance_metadata_url)]
|
23
24
|
end
|
24
25
|
|
25
26
|
##
|
26
27
|
# Return the first provider in the default hierarchy that has a valid token
|
27
28
|
##
|
28
|
-
def
|
29
|
+
def get_credentials_provider
|
29
30
|
@providers.each { |p|
|
30
31
|
begin
|
31
32
|
# if token is assigned, that's the provider we want.
|
32
33
|
# providers must throw NoValueError so that we can fall to the next provider if necessary
|
33
|
-
|
34
|
+
CerberusUtils::get_credentials_from_provider(p)
|
34
35
|
return p
|
35
36
|
|
36
37
|
rescue Cerberus::Exception::NoValueError
|
@@ -39,7 +40,7 @@ module Cerberus
|
|
39
40
|
}
|
40
41
|
|
41
42
|
# we should have found and returned a valid provider above, else there's a problem
|
42
|
-
|
43
|
+
CerberusUtils::Log.instance.error("Could not find a valid provider")
|
43
44
|
raise Cerberus::Exception::NoValidProviders.new
|
44
45
|
end
|
45
46
|
end
|
@@ -4,20 +4,20 @@ module Cerberus
|
|
4
4
|
require_relative('exception/no_value_error')
|
5
5
|
|
6
6
|
##
|
7
|
-
# The default
|
7
|
+
# The default Cerberus URL resolver - looks for #{CERBERUS_URL_ENV_KEY} in env vars
|
8
8
|
##
|
9
9
|
class DefaultUrlResolver
|
10
10
|
|
11
|
-
|
11
|
+
CERBERUS_URL_ENV_KEY = "CERBERUS_ADDR"
|
12
12
|
|
13
13
|
##
|
14
|
-
# Look for the
|
14
|
+
# Look for the cerberus url in the env var
|
15
15
|
##
|
16
|
-
def
|
17
|
-
|
16
|
+
def get_url
|
17
|
+
url_option = ENV[CERBERUS_URL_ENV_KEY]
|
18
18
|
|
19
|
-
if(
|
20
|
-
return
|
19
|
+
if(url_option != nil)
|
20
|
+
return url_option
|
21
21
|
else
|
22
22
|
raise Cerberus::Exception::NoValueError
|
23
23
|
end
|
@@ -5,18 +5,18 @@ module Cerberus
|
|
5
5
|
require_relative('exception/no_value_error')
|
6
6
|
|
7
7
|
##
|
8
|
-
# The Environment variable credentials provider - looks for #{
|
8
|
+
# The Environment variable credentials provider - looks for #{CERBERUS_TOKEN_ENV_KEY} in env vars
|
9
9
|
##
|
10
10
|
class EnvCredentialsProvider
|
11
11
|
|
12
|
-
|
12
|
+
CERBERUS_TOKEN_ENV_KEY = "CERBERUS_TOKEN"
|
13
13
|
|
14
14
|
##
|
15
|
-
# Look for the
|
15
|
+
# Look for the cerberus token in the env var
|
16
16
|
##
|
17
|
-
def
|
17
|
+
def get_client_token
|
18
18
|
|
19
|
-
tokenOption = ENV[
|
19
|
+
tokenOption = ENV[CERBERUS_TOKEN_ENV_KEY]
|
20
20
|
|
21
21
|
if(tokenOption != nil)
|
22
22
|
return tokenOption
|
data/lib/cerberus_client.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require_relative('
|
2
|
-
require_relative('cerberus/
|
1
|
+
require_relative('cerberus_utils/version')
|
2
|
+
require_relative('cerberus/cerberus_client')
|
3
3
|
require_relative('cerberus/default_url_resolver')
|
4
4
|
require_relative('cerberus/default_credentials_provider_chain')
|
5
5
|
require_relative('cerberus/assumed_role_credentials_provider_chain')
|
@@ -7,56 +7,46 @@ require_relative('cerberus/assumed_role_credentials_provider_chain')
|
|
7
7
|
module CerberusClient
|
8
8
|
|
9
9
|
##
|
10
|
-
# Get the
|
10
|
+
# Get the cerberus client using the default cerberus_url_resolver and default credentialsProviderChain
|
11
11
|
##
|
12
|
-
def self.
|
13
|
-
|
14
|
-
return Cerberus::
|
15
|
-
|
12
|
+
def self.get_default_cerberus_client()
|
13
|
+
cerberus_url_resolver = Cerberus::DefaultUrlResolver.new
|
14
|
+
return Cerberus::CerberusClient.new(
|
15
|
+
cerberus_url_resolver,
|
16
|
+
Cerberus::DefaultCredentialsProviderChain.new(cerberus_url_resolver))
|
16
17
|
end
|
17
18
|
|
18
19
|
##
|
19
|
-
# Get the
|
20
|
+
# Get the cerberus client using the provided cerberus_url_resolver and the credentialsProviderChain
|
20
21
|
##
|
21
|
-
def self.
|
22
|
-
|
22
|
+
def self.get_cerberus_client_with_url_resolver(cerberus_url_resolver)
|
23
|
+
return Cerberus::CerberusClient.new(cerberus_url_resolver, Cerberus::DefaultCredentialsProviderChain.new(cerberus_url_resolver))
|
23
24
|
end
|
24
25
|
|
25
26
|
##
|
26
|
-
# Get the
|
27
|
+
# Get the cerberus client using the provided cerberus_url_resolver and the credentialsProviderChain
|
27
28
|
##
|
28
|
-
def self.
|
29
|
-
|
29
|
+
def self.get_cerberus_client_with_metadata_url(ec2_metadata_service_url)
|
30
|
+
cerberus_url_resolver = Cerberus::DefaultUrlResolver.new
|
31
|
+
return Cerberus::CerberusClient.new(
|
32
|
+
cerberus_url_resolver,
|
33
|
+
Cerberus::DefaultCredentialsProviderChain.new(cerberus_url_resolver, nil, ec2_metadata_service_url))
|
30
34
|
end
|
31
35
|
|
32
|
-
def self.getVaultClientForAssumedRole(vaultUrlResolver, roleName, roleRegion, roleAccountId)
|
33
|
-
return Cerberus::VaultClient.new(vaultUrlResolver, Cerberus::AssumedRoleCredentialsProviderChain.new(vaultUrlResolver,
|
34
|
-
nil,
|
35
|
-
roleName,
|
36
|
-
roleRegion,
|
37
|
-
roleAccountId))
|
38
|
-
end
|
39
|
-
|
40
|
-
|
41
36
|
##
|
42
|
-
# Get
|
37
|
+
# Get the cerberus client using the provided cerberus_url_resolver and the credentialsProviderChain
|
43
38
|
##
|
44
|
-
def self.
|
45
|
-
return
|
39
|
+
def self.get_cerberus_client(cerberus_url_resolver, credentialsProviderChain)
|
40
|
+
return Cerberus::CerberusClient.new(cerberus_url_resolver, credentialsProviderChain)
|
46
41
|
end
|
47
42
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
##
|
56
|
-
# Get url from implementing url resolver
|
57
|
-
##
|
58
|
-
def self.getUrlFromResolver(vaultUrlResolver)
|
59
|
-
return vaultUrlResolver.getUrl
|
43
|
+
def self.get_cerberus_client_for_assumed_role(cerberus_url_resolver, iam_principal_arn, region)
|
44
|
+
return Cerberus::CerberusClient.new(
|
45
|
+
cerberus_url_resolver,
|
46
|
+
Cerberus::AssumedRoleCredentialsProviderChain.new(
|
47
|
+
cerberus_url_resolver,
|
48
|
+
iam_principal_arn,
|
49
|
+
region))
|
60
50
|
end
|
61
51
|
|
62
52
|
end # CerberusClient module
|
@@ -1,5 +1,5 @@
|
|
1
1
|
|
2
|
-
module
|
2
|
+
module CerberusUtils
|
3
3
|
|
4
4
|
require_relative('../cerberus/exception/http_error')
|
5
5
|
require('net/http')
|
@@ -15,14 +15,14 @@ module CerberusClient
|
|
15
15
|
# Generic HTTP handler for Cerberus Client needs
|
16
16
|
# uri: the fully qualified URI object to call
|
17
17
|
# method: the HTTP method to use'
|
18
|
-
#
|
19
|
-
#
|
18
|
+
# use_ssl: boolean - use HTTPS or not
|
19
|
+
# json_data: if not nil, made the request body and 'Content-Type' header is set to "application/json"
|
20
20
|
# headers: if not nil, should be a HashMap of header Key, Values
|
21
21
|
##
|
22
|
-
def
|
22
|
+
def make_http_call(uri, method, use_ssl, json_data = nil, headers_map = nil)
|
23
23
|
|
24
24
|
begin
|
25
|
-
|
25
|
+
CerberusUtils::Log.instance.debug("Http::make_http_call -> uri: #{uri}, method: #{method}, use_ssl: #{use_ssl}, json_data: #{json_data}")
|
26
26
|
|
27
27
|
http = Net::HTTP.new(uri.host, uri.port)
|
28
28
|
|
@@ -36,12 +36,13 @@ module CerberusClient
|
|
36
36
|
raise NotImplementedError
|
37
37
|
end
|
38
38
|
|
39
|
-
if(
|
39
|
+
if(json_data != nil); request.body = "#{json_data}"; request['Content-Type'] = "application/json"; end
|
40
40
|
|
41
|
-
if(
|
42
|
-
request['X-Cerberus-Client'] = "CerberusRubyClient/#{
|
41
|
+
if(headers_map != nil); headers_map.each{ |headerKey, headerValue| request[headerKey] = headerValue } end
|
42
|
+
request['X-Cerberus-Client'] = "CerberusRubyClient/#{CerberusUtils::VERSION}"
|
43
43
|
|
44
|
-
|
44
|
+
puts request
|
45
|
+
http.use_ssl = use_ssl
|
45
46
|
response = http.request(request)
|
46
47
|
|
47
48
|
# this is just for convenience handling down the stack... response object inclucded with the exception
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module CerberusUtils
|
2
|
+
|
3
|
+
##
|
4
|
+
# Get credentials from implementing credential_provider
|
5
|
+
##
|
6
|
+
def self.get_credentials_from_provider(credential_provider)
|
7
|
+
return credential_provider.get_client_token
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Get credentials provider from chain implementing get get_credentials_provider
|
12
|
+
##
|
13
|
+
def self.get_credentials_provider_from_chain(credential_provider_chain)
|
14
|
+
return credential_provider_chain.get_credentials_provider
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Get url from implementing url resolver
|
19
|
+
##
|
20
|
+
def self.get_url_from_resolver(cerberus_url_resolver)
|
21
|
+
return cerberus_url_resolver.get_url
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|