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: 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