cerberus_client 1.4.0 → 1.5.0.rc.3
Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42c674693d53762da3e59b70817e1ee860464d2d
|
4
|
+
data.tar.gz: c3eabb9413e9c5f8aef01d7e830eef06b00fa035
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69f2092ca99b07c4b5f171f7cb462f647e75a82b59490241e6d016d1569d733a449d612fc2eb8c789a0b313a92ce9ef5b7f9428b7ecc2a1ccf967fb6af88869a
|
7
|
+
data.tar.gz: b3b8fbede4e791182cb5effd4a5f9668ee4e6df3141559bcef595132ae75f9e5931eafbc30fbbc5e155190801bb0e1acfcd1af8e68b7304a396efeaba118cfe6
|
data/README.md
CHANGED
@@ -90,6 +90,10 @@ Then open Interactive Ruby:
|
|
90
90
|
2.2.2 :001 > vaultClient.read("app/example/test")
|
91
91
|
```
|
92
92
|
|
93
|
+
** Note: If a `LoadError` is thrown with a message `cannot load such file` this may be because
|
94
|
+
you have added new files. Commit the new files and re-build your gem to ensure the new files
|
95
|
+
get included in your gem release candidate .
|
96
|
+
|
93
97
|
### Running in AWS
|
94
98
|
|
95
99
|
If the environment variables used in local development are not found, the client will try to use the AWS metadata
|
@@ -0,0 +1,189 @@
|
|
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 AwsPrincipalCredentialsProvider
|
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
|
+
# relative URI to look up IAM role in AWS metadata svc
|
29
|
+
IAM_ROLE_NAME_REL_URI = "/iam/security-credentials/"
|
30
|
+
# magic number is the index into a split role ARN to grab the acccount ID
|
31
|
+
ROLE_ARN_ARRAY_INDEX_OF_ACCOUNTNUM = 4
|
32
|
+
# magic number is the index into a split role ARN to grab the role name
|
33
|
+
ROLE_ARN_ARRAY_INDEX_OF_ROLENAME = 1
|
34
|
+
# relative URI to get encrypted auth data from Cerberus
|
35
|
+
ROLE_AUTH_REL_URI = "/v2/auth/iam-principal"
|
36
|
+
# reference into the decrypted auth data json we get from Cerberus
|
37
|
+
CERBERUS_AUTH_DATA_CLIENT_TOKEN_KEY = "client_token"
|
38
|
+
CERBERUS_AUTH_DATA_LEASE_DURATION_KEY = "lease_duration"
|
39
|
+
CERBERUS_AUTH_DATA_POLICIES_KEY = "policies"
|
40
|
+
|
41
|
+
LOGGER = CerberusClient::Log.instance
|
42
|
+
|
43
|
+
##
|
44
|
+
# Init AWS principal provider - needs vault base url
|
45
|
+
##
|
46
|
+
def initialize(vaultBaseUrl)
|
47
|
+
@vaultBaseUrl = vaultBaseUrl
|
48
|
+
@clientToken = nil
|
49
|
+
@instanceMdSvcBaseUrl = INSTANCE_METADATA_SVC_BASE_URL
|
50
|
+
@role = getRoleFromInstanceMetadata
|
51
|
+
|
52
|
+
LOGGER.debug("AwsPrincipalCredentialsProvider initialized with vault base url #{@vaultBaseUrl}")
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Get credentials using AWS IAM role
|
57
|
+
##
|
58
|
+
def getClientToken
|
59
|
+
|
60
|
+
if (@role.nil?)
|
61
|
+
raise Cerberus::Exception::NoValueError
|
62
|
+
end
|
63
|
+
|
64
|
+
if (@clientToken.nil?)
|
65
|
+
@clientToken = getCredentialsFromCerberus
|
66
|
+
end
|
67
|
+
|
68
|
+
# using two if statements here just to make the logging easier..
|
69
|
+
# the above we expect on startup, expiration is an interesting event worth a debug log all its own
|
70
|
+
if (@clientToken.expired?)
|
71
|
+
LOGGER.debug("Existing ClientToken has expired - refreshing from Cerberus...")
|
72
|
+
@clientToken = getCredentialsFromCerberus
|
73
|
+
end
|
74
|
+
|
75
|
+
return @clientToken.authToken
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Use the instance metadata to extract the role information
|
81
|
+
# This function should only be called from an EC2 instance otherwise the http
|
82
|
+
# call will fail.
|
83
|
+
##
|
84
|
+
def getRoleFromInstanceMetadata
|
85
|
+
role_arn = getIAMRoleARN
|
86
|
+
region = getRegionFromAZ(getAvailabilityZone)
|
87
|
+
account_id = getAccountIdFromRoleARN(role_arn)
|
88
|
+
role_name = getIAMRoleName
|
89
|
+
|
90
|
+
LOGGER.debug("roleARN #{role_arn}")
|
91
|
+
LOGGER.debug("region #{region}")
|
92
|
+
LOGGER.debug("accountId #{account_id}")
|
93
|
+
LOGGER.debug("roleName #{role_name}")
|
94
|
+
|
95
|
+
return AwsRoleInfo.new(role_name, region, account_id, nil)
|
96
|
+
end
|
97
|
+
|
98
|
+
##
|
99
|
+
# Reach out to the Cerberus management service and get an auth token
|
100
|
+
##
|
101
|
+
def getCredentialsFromCerberus
|
102
|
+
begin
|
103
|
+
authData = doAuthWithCerberus(@role.account_id, @role.name, @role.region)
|
104
|
+
|
105
|
+
LOGGER.debug("Got auth data from Cerberus. Attempting to decrypt...")
|
106
|
+
|
107
|
+
# decrypt the data we got from cerberus to get the vault token
|
108
|
+
kms = Aws::KMS::Client.new(region: @role.region, credentials: @role.credentials[:credentials])
|
109
|
+
|
110
|
+
decryptedAuthDataJson = JSON.parse(kms.decrypt(ciphertext_blob: Base64.decode64(authData)).plaintext)
|
111
|
+
|
112
|
+
LOGGER.debug("Decrypt successful. Passing back Cerberus auth token.")
|
113
|
+
# pass back a credentials object that will allow us to reuse it until it expires
|
114
|
+
CerberusClientToken.new(decryptedAuthDataJson[CERBERUS_AUTH_DATA_CLIENT_TOKEN_KEY],
|
115
|
+
decryptedAuthDataJson[CERBERUS_AUTH_DATA_LEASE_DURATION_KEY],
|
116
|
+
decryptedAuthDataJson[CERBERUS_AUTH_DATA_POLICIES_KEY])
|
117
|
+
|
118
|
+
rescue Cerberus::Exception::HttpError
|
119
|
+
# catch http errors here and assert no value
|
120
|
+
# this may not actually be the case, there are legitimate reasons HTTP can fail when it "should" work
|
121
|
+
# but this is handled by logging - a warning is set in the log in during the HTTP call
|
122
|
+
raise Cerberus::Exception::NoValueError
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
##
|
127
|
+
# Get the AWS account ID from the role ARN
|
128
|
+
# Expects formatting [some value]:[some value]:[some value]::[account id]
|
129
|
+
##
|
130
|
+
def getAccountIdFromRoleARN(roleARN)
|
131
|
+
roleARN.split(':')[ROLE_ARN_ARRAY_INDEX_OF_ACCOUNTNUM]
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Get the role name from EC@ Metadata
|
136
|
+
##
|
137
|
+
def getIAMRoleName
|
138
|
+
response = doHttpToMDService(IAM_ROLE_NAME_REL_URI)
|
139
|
+
response.body
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# Read the IAM role ARN from the instance metadata
|
144
|
+
# Will throw an HTTP exception if there is no IAM role associated with the instance
|
145
|
+
##
|
146
|
+
def getIAMRoleARN
|
147
|
+
response = doHttpToMDService(IAM_ROLE_INFO_REL_URI)
|
148
|
+
jsonResponseBody = JSON.parse(response.body)
|
149
|
+
jsonResponseBody[IAM_ROLE_ARN_KEY]
|
150
|
+
end
|
151
|
+
|
152
|
+
##
|
153
|
+
# Get the region from AWS instance metadata
|
154
|
+
##
|
155
|
+
def getAvailabilityZone
|
156
|
+
doHttpToMDService(REGION_REL_URI).body
|
157
|
+
end
|
158
|
+
|
159
|
+
##
|
160
|
+
# Get region from AZ
|
161
|
+
##
|
162
|
+
def getRegionFromAZ(az)
|
163
|
+
az[0, az.length-1]
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Call the instance metadata service with a relative URI and return the response if the call succeeds
|
168
|
+
# else throw an IOError for non-2xx responses and RuntimeError for any exceptions down the stack
|
169
|
+
##
|
170
|
+
def doHttpToMDService(relUri)
|
171
|
+
url = URI(@instanceMdSvcBaseUrl + relUri)
|
172
|
+
CerberusClient::Http.new.doHttp(url, 'GET', false)
|
173
|
+
end
|
174
|
+
|
175
|
+
##
|
176
|
+
#
|
177
|
+
##
|
178
|
+
def doAuthWithCerberus(accountId, roleName, region)
|
179
|
+
postJsonData = JSON.generate({:iam_principal_arn => "arn:aws:iam::#{accountId}:role/#{roleName}", :region => region})
|
180
|
+
authUrl = URI(@vaultBaseUrl + ROLE_AUTH_REL_URI)
|
181
|
+
useSSL = ! ("#{@vaultBaseUrl}".include? "localhost")
|
182
|
+
authResponse = CerberusClient::Http.new.doHttp(authUrl, 'POST', useSSL, postJsonData)
|
183
|
+
# if we got this far, we should have a valid response with encrypted data
|
184
|
+
# send back the encrypted data
|
185
|
+
JSON.parse(authResponse.body)['auth_data']
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
@@ -3,6 +3,7 @@ require_relative('../cerberus_client/log')
|
|
3
3
|
require_relative('exception/no_value_error')
|
4
4
|
require_relative('exception/no_valid_providers')
|
5
5
|
require_relative('aws_role_credentials_provider')
|
6
|
+
require_relative('aws_principal_credentials_provider')
|
6
7
|
require_relative('env_credentials_provider')
|
7
8
|
|
8
9
|
module Cerberus
|
@@ -17,7 +18,8 @@ module Cerberus
|
|
17
18
|
|
18
19
|
# return default array of providers
|
19
20
|
@providers = [Cerberus::EnvCredentialsProvider.new,
|
20
|
-
Cerberus::AwsRoleCredentialsProvider.new(vaultBaseUrl, instanceMdSvcBaseUrl)
|
21
|
+
Cerberus::AwsRoleCredentialsProvider.new(vaultBaseUrl, instanceMdSvcBaseUrl),
|
22
|
+
Cerberus::AwsPrincipalCredentialsProvider.new(vaultBaseUrl)]
|
21
23
|
end
|
22
24
|
|
23
25
|
##
|
@@ -41,4 +43,4 @@ module Cerberus
|
|
41
43
|
raise Cerberus::Exception::NoValidProviders.new
|
42
44
|
end
|
43
45
|
end
|
44
|
-
end
|
46
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cerberus_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0.rc.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Teibel
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-08-
|
11
|
+
date: 2017-08-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk
|
@@ -73,6 +73,7 @@ files:
|
|
73
73
|
- README.md
|
74
74
|
- cerberus_client.gemspec
|
75
75
|
- lib/cerberus/assumed_role_credentials_provider_chain.rb
|
76
|
+
- lib/cerberus/aws_principal_credentials_provider.rb
|
76
77
|
- lib/cerberus/aws_role_credentials_provider.rb
|
77
78
|
- lib/cerberus/aws_role_info.rb
|
78
79
|
- lib/cerberus/cerberus_client_token.rb
|
@@ -105,9 +106,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
105
106
|
version: '0'
|
106
107
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
108
|
requirements:
|
108
|
-
- - '
|
109
|
+
- - '>'
|
109
110
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
111
|
+
version: 1.3.1
|
111
112
|
requirements: []
|
112
113
|
rubyforge_project:
|
113
114
|
rubygems_version: 2.2.2
|