cerberus_client 1.4.0 → 1.5.0.rc.3
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
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
|