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: ccb02d6f202ae5383140498f5bb9654e3efb0ea0
4
- data.tar.gz: f639c3ad2c68c3af46ba48d28cd0668f0c213820
3
+ metadata.gz: 42c674693d53762da3e59b70817e1ee860464d2d
4
+ data.tar.gz: c3eabb9413e9c5f8aef01d7e830eef06b00fa035
5
5
  SHA512:
6
- metadata.gz: 3c68b365555fe8e84d9eaea409f9694605580698a4bdefd79add1759c588eab6ffdc42c89b2885ce03aa23a4c66b68f2180c2e833384c28664f38c0425aedd41
7
- data.tar.gz: 1f549a900c19e7c23f813520d672d4f3b961ebfe82721fbf29137bdb63d27932bff81b146ca5cf6862efea2b0b2420d8abaaf992a7ba30f65e757339822256ef
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
@@ -1,3 +1,3 @@
1
1
  module CerberusClient
2
- VERSION = "1.4.0"
2
+ VERSION = "1.5.0.rc.3"
3
3
  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.0
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-07 00:00:00.000000000 Z
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: '0'
111
+ version: 1.3.1
111
112
  requirements: []
112
113
  rubyforge_project:
113
114
  rubygems_version: 2.2.2