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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e2a7a6136f4da9d17b5b9d4622a9da222f194caf
|
4
|
+
data.tar.gz: 3711fd81590a62ae5cf734e29320344125a3f07a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 87b641d09dc280510f4f1cc739e306a615d91f4e4eeec221c2d4594e965331f5ef956ea3a4c8ce47118e242b671bbcb7ce6f845fd59bc07e993890fc985ff8f1
|
7
|
+
data.tar.gz: eafaa3f634a9adaeece7ef0a48e940a90901c53628da6384f2cec71799becfe877399088c0979617eb7e941f27f027fe5837eb8beae020157b6aa3d4092dca73
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
[](https://travis-ci.org/Nike-Inc/cerberus-ruby-client)
|
6
6
|
|
7
7
|
|
8
|
-
This is a Ruby based client library for communicating with
|
8
|
+
This is a Ruby based client library for communicating with Ceberus via HTTP and enables authentication schemes specific
|
9
9
|
to AWS and Cerberus.
|
10
10
|
|
11
11
|
This client currently supports read-only operations (write operations are not yet implemented, feel free to open a
|
@@ -39,7 +39,11 @@ $ gem install cerberus_client
|
|
39
39
|
Please start by reading the [Cerberus quick start guide](http://engineering.nike.com/cerberus/docs/user-guide/quick-start).
|
40
40
|
|
41
41
|
```ruby
|
42
|
-
|
42
|
+
cerberus_client = CerberusClient::get_default_cerberus_client()
|
43
|
+
```
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
cerberus_client = CerberusClient::get_cerberus_client_for_assumed_role(Cerberus::DefaultUrlResolver.new, "arn:aws:iam::<account_id>:role/<role_name>", "us-west-2")
|
43
47
|
```
|
44
48
|
|
45
49
|
There are two ways Cerberus clients are typically used:
|
@@ -49,7 +53,7 @@ There are two ways Cerberus clients are typically used:
|
|
49
53
|
|
50
54
|
### Local Development
|
51
55
|
|
52
|
-
The example Ruby code above uses the DefaultUrlResolver to resolve the URL for
|
56
|
+
The example Ruby code above uses the DefaultUrlResolver to resolve the URL for Cerberus. For that to succeed, the
|
53
57
|
environment variable, CERBERUS_ADDR, must be set:
|
54
58
|
```bash
|
55
59
|
export CERBERUS_ADDR=https://cerberus.example.com
|
@@ -60,12 +64,12 @@ export CERBERUS_ADDR=https://localhost:9001
|
|
60
64
|
```
|
61
65
|
|
62
66
|
The example above also use the DefaultCredentialsProviderChain which is used to resolve the token needed to interact
|
63
|
-
with
|
67
|
+
with Cerberus. This chain will first look to see if an environemnt variable has been set with a cerberus token, e.g.
|
64
68
|
```bash
|
65
69
|
export CERBERUS_TOKEN=9cfced14-91ae-e3ad-5b9d-1cae6c82362d
|
66
70
|
```
|
67
71
|
|
68
|
-
Increment the version and add `.rc.1` to the end in the `lib/
|
72
|
+
Increment the version and add `.rc.1` to the end in the `lib/cerberus_utils/version.rb` file.
|
69
73
|
|
70
74
|
Then build and install the gem locally:
|
71
75
|
|
@@ -86,7 +90,7 @@ Then open Interactive Ruby:
|
|
86
90
|
% irb
|
87
91
|
|
88
92
|
2.2.2 :001 > require 'cerberus_client'
|
89
|
-
2.2.2 :001 > vaultClient = CerberusClient::
|
93
|
+
2.2.2 :001 > vaultClient = CerberusClient::get_default_vault_client()
|
90
94
|
2.2.2 :001 > vaultClient.read("app/example/test")
|
91
95
|
```
|
92
96
|
|
data/cerberus_client.gemspec
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require '
|
4
|
+
require 'cerberus_utils/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "cerberus_client"
|
8
|
-
spec.version =
|
9
|
-
spec.authors = ["Joe Teibel"]
|
10
|
-
spec.email = ["
|
8
|
+
spec.version = CerberusUtils::VERSION
|
9
|
+
spec.authors = ["Shaun Ford", "Joe Teibel"]
|
10
|
+
spec.email = ["shaun.ford@nike.com"]
|
11
11
|
|
12
12
|
spec.summary = ["A Ruby Client for Cerberus, a secure property store for cloud applications"]
|
13
|
-
spec.description = "This is a Ruby based client library for communicating with
|
13
|
+
spec.description = "This is a Ruby based client library for communicating with Cerberus via HTTP and enables authentication schemes specific to AWS and Cerberus. This client currently supports read-only operations (write operations are not yet implemented, feel free to open a pull request to implement write operations). To learn more about Cerberus, please visit the Cerberus website."
|
14
14
|
spec.homepage = "https://github.com/Nike-Inc/cerberus-ruby-client"
|
15
15
|
spec.license = "Apache License Version 2"
|
16
16
|
|
@@ -1,8 +1,9 @@
|
|
1
1
|
require_relative('../cerberus_client')
|
2
|
-
require_relative('../
|
2
|
+
require_relative('../cerberus_utils/utils')
|
3
|
+
require_relative('../cerberus_utils/log')
|
3
4
|
require_relative('exception/no_value_error')
|
4
5
|
require_relative('exception/no_valid_providers')
|
5
|
-
require_relative('
|
6
|
+
require_relative('aws_assumed_role_credentials_provider')
|
6
7
|
require_relative('env_credentials_provider')
|
7
8
|
|
8
9
|
module Cerberus
|
@@ -11,24 +12,23 @@ module Cerberus
|
|
11
12
|
# Default credentials provider chain
|
12
13
|
##
|
13
14
|
class AssumedRoleCredentialsProviderChain
|
14
|
-
def initialize(
|
15
|
-
vaultBaseUrl = CerberusClient.getUrlFromResolver(urlResolver)
|
15
|
+
def initialize(url_resolver, iam_role_arn, region)
|
16
16
|
|
17
17
|
# return default array of providers
|
18
18
|
@providers = [Cerberus::EnvCredentialsProvider.new,
|
19
|
-
Cerberus::
|
19
|
+
Cerberus::AwsAssumeRoleCredentialsProvider.new(url_resolver, iam_role_arn, region)]
|
20
20
|
end
|
21
21
|
|
22
22
|
|
23
23
|
##
|
24
24
|
# Return the first provider in the default hierarchy that has a valid token
|
25
25
|
##
|
26
|
-
def
|
26
|
+
def get_credentials_provider
|
27
27
|
@providers.each { |p|
|
28
28
|
begin
|
29
29
|
# if token is assigned, that's the provider we want.
|
30
30
|
# providers must throw NoValueError so that we can fall to the next provider if necessary
|
31
|
-
|
31
|
+
CerberusUtils::get_credentials_from_provider(p)
|
32
32
|
return p
|
33
33
|
|
34
34
|
rescue Cerberus::Exception::NoValueError
|
@@ -37,7 +37,7 @@ module Cerberus
|
|
37
37
|
}
|
38
38
|
|
39
39
|
# we should have found and returned a valid provider above, else there's a problem
|
40
|
-
|
40
|
+
CerberusUtils::Log.instance.error("Could not find a valid provider")
|
41
41
|
raise Cerberus::Exception::NoValidProviders.new
|
42
42
|
end
|
43
43
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Cerberus
|
2
|
+
|
3
|
+
require_relative('../cerberus_utils/log')
|
4
|
+
require_relative('../cerberus_utils/http')
|
5
|
+
require_relative('../cerberus_utils/utils')
|
6
|
+
require_relative('../cerberus_client')
|
7
|
+
require_relative('exception/no_value_error')
|
8
|
+
require_relative('exception/http_error')
|
9
|
+
require_relative('cerberus_client_token')
|
10
|
+
require_relative('cerberus_auth_info')
|
11
|
+
require('aws-sdk')
|
12
|
+
require('net/http')
|
13
|
+
require('json')
|
14
|
+
require('base64')
|
15
|
+
require('securerandom')
|
16
|
+
|
17
|
+
##
|
18
|
+
# The AWS IAM role credentials provider
|
19
|
+
#
|
20
|
+
# Tries to authenticate with Cerberus using the given IAM role
|
21
|
+
##
|
22
|
+
class AwsAssumeRoleCredentialsProvider
|
23
|
+
|
24
|
+
# relative URI to get encrypted auth data from Cerberus
|
25
|
+
ROLE_AUTH_REL_URI = "/v2/auth/iam-principal"
|
26
|
+
# reference into the decrypted auth data json we get from Cerberus
|
27
|
+
CERBERUS_AUTH_DATA_CLIENT_TOKEN_KEY = "client_token"
|
28
|
+
CERBERUS_AUTH_DATA_LEASE_DURATION_KEY = "lease_duration"
|
29
|
+
CERBERUS_AUTH_DATA_POLICIES_KEY = "policies"
|
30
|
+
|
31
|
+
$stdout.sync = true
|
32
|
+
LOGGER = CerberusUtils::Log.instance
|
33
|
+
|
34
|
+
##
|
35
|
+
# Init AWS role provider - needs cerberus base url. Instance metadata service url is optional to make unit tests
|
36
|
+
# easier and so we can provide a hook to set this via config as needed
|
37
|
+
##
|
38
|
+
def initialize(cerberus_url_resolver, iam_role_arn, region)
|
39
|
+
@cerberus_base_url = CerberusUtils::get_url_from_resolver(cerberus_url_resolver)
|
40
|
+
@client_token = nil
|
41
|
+
@cerberus_auth_info = get_assumed_role_info(iam_role_arn, region)
|
42
|
+
|
43
|
+
LOGGER.debug("AwsAssumeRoleCredentialsProvider initialized with cerberus base url #{@cerberus_base_url}")
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Get credentials using AWS IAM role
|
48
|
+
##
|
49
|
+
def get_client_token
|
50
|
+
|
51
|
+
if (@cerberus_auth_info.nil?)
|
52
|
+
LOGGER.warn("Instance metadata URL is nil for role provider!")
|
53
|
+
raise Cerberus::Exception::NoValueError
|
54
|
+
end
|
55
|
+
|
56
|
+
if (@client_token.nil?)
|
57
|
+
@client_token = get_credentials_from_cerberus
|
58
|
+
end
|
59
|
+
|
60
|
+
# using two if statements here just to make the logging easier..
|
61
|
+
# the above we expect on startup, expiration is an interesting event worth a debug log all its own
|
62
|
+
if (@client_token.expired?)
|
63
|
+
LOGGER.debug("Existing ClientToken has expired - refreshing from Cerberus...")
|
64
|
+
@client_token = get_credentials_from_cerberus
|
65
|
+
end
|
66
|
+
|
67
|
+
return @client_token.authToken
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
##
|
74
|
+
# Get an CerberusAuthInfo object from the provided data
|
75
|
+
##
|
76
|
+
def get_assumed_role_info(iam_role_arn, region)
|
77
|
+
|
78
|
+
begin
|
79
|
+
num_chars = 10 # magic number - shouldn't be too long to avoide session name length limits
|
80
|
+
random_string = SecureRandom.hex(num_chars/2) # hex method produces double the inputted num
|
81
|
+
assume_role_session_name = "cerb-assume-role-session-#{random_string}"
|
82
|
+
|
83
|
+
LOGGER.debug("role: #{iam_role_arn}")
|
84
|
+
LOGGER.debug("region: #{region}")
|
85
|
+
LOGGER.debug("session name: #{assume_role_session_name}")
|
86
|
+
|
87
|
+
role_creds = Aws::AssumeRoleCredentials.new(
|
88
|
+
client: Aws::STS::Client.new(region: region),
|
89
|
+
role_arn: iam_role_arn,
|
90
|
+
role_session_name: assume_role_session_name)
|
91
|
+
|
92
|
+
return CerberusAuthInfo.new(iam_role_arn, region, credentials: role_creds)
|
93
|
+
rescue
|
94
|
+
LOGGER.error("Failed to assume role: #{iam_role_arn}, region: #{region}")
|
95
|
+
return nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Reach out to the Cerberus management service and get an auth token
|
101
|
+
##
|
102
|
+
def get_credentials_from_cerberus
|
103
|
+
LOGGER.debug("Authenticating with assumed role...")
|
104
|
+
begin
|
105
|
+
authData = do_auth_with_cerberus(@cerberus_auth_info.iam_principal_arn, @cerberus_auth_info.region)
|
106
|
+
|
107
|
+
LOGGER.debug("Got auth data from Cerberus. Attempting to decrypt...")
|
108
|
+
|
109
|
+
# decrypt the data we got from cerberus to get the cerberus token
|
110
|
+
kms = Aws::KMS::Client.new(region: @cerberus_auth_info.region, credentials: @cerberus_auth_info.credentials[:credentials])
|
111
|
+
|
112
|
+
decryptedAuthDataJson = JSON.parse(kms.decrypt(ciphertext_blob: Base64.decode64(authData)).plaintext)
|
113
|
+
|
114
|
+
LOGGER.debug("Decrypt successful. Passing back Cerberus auth token.")
|
115
|
+
# pass back a credentials object that will allow us to reuse it until it expires
|
116
|
+
CerberusClientToken.new(decryptedAuthDataJson[CERBERUS_AUTH_DATA_CLIENT_TOKEN_KEY],
|
117
|
+
decryptedAuthDataJson[CERBERUS_AUTH_DATA_LEASE_DURATION_KEY],
|
118
|
+
decryptedAuthDataJson[CERBERUS_AUTH_DATA_POLICIES_KEY])
|
119
|
+
|
120
|
+
rescue Cerberus::Exception::HttpError
|
121
|
+
# catch http errors here and assert no value
|
122
|
+
# this may not actually be the case, there are legitimate reasons HTTP can fail when it "should" work
|
123
|
+
# but this is handled by logging - a warning is set in the log in during the HTTP call
|
124
|
+
LOGGER.error("Failed to authenticate with assumed role: #{@cerberus_auth_info.iam_principal_arn},
|
125
|
+
region: #{@cerberus_auth_info.region}.")
|
126
|
+
raise Cerberus::Exception::NoValueError
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
#
|
132
|
+
##
|
133
|
+
def do_auth_with_cerberus(iam_principal_arn, region)
|
134
|
+
post_json_data = JSON.generate({:iam_principal_arn => iam_principal_arn, :region => region})
|
135
|
+
auth_url = URI(@cerberus_base_url + ROLE_AUTH_REL_URI)
|
136
|
+
use_ssl = ! @cerberus_base_url.include?("localhost")
|
137
|
+
auth_response = CerberusUtils::Http.new.make_http_call(auth_url, 'POST', use_ssl, post_json_data)
|
138
|
+
# if we got this far, we should have a valid response with encrypted data
|
139
|
+
# send back the encrypted data
|
140
|
+
JSON.parse(auth_response.body)['auth_data']
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
@@ -1,34 +1,37 @@
|
|
1
1
|
module Cerberus
|
2
2
|
|
3
|
-
require_relative('../
|
4
|
-
require_relative('../
|
3
|
+
require_relative('../cerberus_utils/log')
|
4
|
+
require_relative('../cerberus_utils/http')
|
5
|
+
require_relative('../cerberus_utils/utils')
|
5
6
|
require_relative('../cerberus_client')
|
6
7
|
require_relative('exception/no_value_error')
|
7
8
|
require_relative('exception/http_error')
|
8
9
|
require_relative('cerberus_client_token')
|
9
|
-
require_relative('
|
10
|
+
require_relative('cerberus_auth_info')
|
10
11
|
require('aws-sdk')
|
11
12
|
require('net/http')
|
12
13
|
require('json')
|
13
14
|
require('base64')
|
14
15
|
|
15
16
|
##
|
16
|
-
# The AWS IAM
|
17
|
+
# The AWS IAM principal credentials provider
|
18
|
+
#
|
19
|
+
# Tries to authenticate with Cerberus using the IAM role of the EC2 instance
|
17
20
|
##
|
18
21
|
class AwsPrincipalCredentialsProvider
|
19
22
|
|
20
23
|
# AWS metadata instance URL
|
21
|
-
|
24
|
+
AWS_EC2_METADATA_URL = "http://169.254.169.254/latest/meta-data"
|
22
25
|
# relative URI to look up AZ in AWS metadata svc
|
23
26
|
REGION_REL_URI = "/placement/availability-zone"
|
24
27
|
# relative URI to look up IAM role in AWS metadata svc
|
25
|
-
|
28
|
+
EC2_INSTNACE_PROFILE_REL_URI = "/iam/info"
|
26
29
|
# reference into the metadata data json we get to look up IAM role
|
27
|
-
|
30
|
+
EC2_INSTANCE_PROFILE_ARN_KEY = 'InstanceProfileArn'
|
28
31
|
# relative URI to look up IAM role in AWS metadata svc
|
29
32
|
IAM_ROLE_NAME_REL_URI = "/iam/security-credentials/"
|
30
33
|
# magic number is the index into a split role ARN to grab the acccount ID
|
31
|
-
|
34
|
+
ROLE_ARN_ARRAY_INDEX_OF_ACCOUNT_NUM = 4
|
32
35
|
# magic number is the index into a split role ARN to grab the role name
|
33
36
|
ROLE_ARN_ARRAY_INDEX_OF_ROLENAME = 1
|
34
37
|
# relative URI to get encrypted auth data from Cerberus
|
@@ -38,86 +41,106 @@ module Cerberus
|
|
38
41
|
CERBERUS_AUTH_DATA_LEASE_DURATION_KEY = "lease_duration"
|
39
42
|
CERBERUS_AUTH_DATA_POLICIES_KEY = "policies"
|
40
43
|
|
41
|
-
|
44
|
+
$stdout.sync = true
|
45
|
+
LOGGER = CerberusUtils::Log.instance
|
42
46
|
|
43
47
|
##
|
44
|
-
# Init AWS principal provider - needs
|
48
|
+
# Init AWS principal provider - needs cerberus base url
|
45
49
|
##
|
46
|
-
def initialize(
|
47
|
-
@
|
48
|
-
@
|
49
|
-
@
|
50
|
+
def initialize(cerberus_url_resolver, region = nil, instance_metadata_url = AWS_EC2_METADATA_URL)
|
51
|
+
@cerberus_base_url = CerberusUtils::get_url_from_resolver(cerberus_url_resolver)
|
52
|
+
@client_token = nil
|
53
|
+
@instance_metadata_url = instance_metadata_url
|
54
|
+
@cerberus_auth_info = get_cerberus_auth_info(instance_metadata_url, region)
|
50
55
|
|
51
|
-
LOGGER.debug("AwsPrincipalCredentialsProvider initialized with
|
56
|
+
LOGGER.debug("AwsPrincipalCredentialsProvider initialized with cerberus base url #{@cerberus_base_url}")
|
52
57
|
end
|
53
58
|
|
54
59
|
##
|
55
60
|
# Get credentials using AWS IAM role
|
56
61
|
##
|
57
|
-
def
|
62
|
+
def get_client_token
|
58
63
|
|
59
|
-
if (@
|
64
|
+
if (@cerberus_auth_info.nil?)
|
60
65
|
raise Cerberus::Exception::NoValueError
|
61
66
|
end
|
62
67
|
|
63
|
-
if (@
|
64
|
-
@
|
68
|
+
if (@client_token.nil?)
|
69
|
+
@client_token = get_credentials_from_cerberus
|
65
70
|
end
|
66
71
|
|
67
|
-
# using two if statements
|
68
|
-
# the above we expect on startup, expiration is
|
69
|
-
if (@
|
70
|
-
LOGGER.debug("Existing
|
71
|
-
@
|
72
|
+
# using two if statements for nil v. expired makes logging easier..
|
73
|
+
# the above we expect on startup, expiration is worth its own logging
|
74
|
+
if (@client_token.expired?)
|
75
|
+
LOGGER.debug("Existing client token has expired - refreshing from Cerberus...")
|
76
|
+
@client_token = get_credentials_from_cerberus
|
72
77
|
end
|
73
78
|
|
74
|
-
return @
|
79
|
+
return @client_token.authToken
|
75
80
|
|
76
81
|
end
|
77
82
|
|
78
83
|
private
|
79
84
|
|
80
85
|
##
|
81
|
-
# Uses provided data to determine how to construct the
|
86
|
+
# Uses provided data to determine how to construct the CerberusAuthInfo for use by this provider
|
82
87
|
##
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
+
def get_cerberus_auth_info(instance_metadata_url, region)
|
89
|
+
LOGGER.debug("Getting IAM role info...")
|
90
|
+
|
91
|
+
# if we have no metedata about how to auth, we do nothing
|
92
|
+
# this is used in unit testing primarily
|
93
|
+
if (instance_metadata_url.nil?)
|
94
|
+
LOGGER.warn("Instance metadata URL is nil for role provider!")
|
95
|
+
return nil;
|
96
|
+
else
|
97
|
+
# collect instance metadata we need to auth with Cerberus
|
98
|
+
return get_role_from_ec2_metadata(region)
|
88
99
|
end
|
89
100
|
end
|
90
101
|
|
91
102
|
##
|
92
103
|
# Use the instance metadata to extract the role information
|
104
|
+
#
|
105
|
+
# Gets the IAM role name and account ID in a weird way due to how the
|
106
|
+
# EC2 Metadata service disjointly provides the data
|
107
|
+
#
|
93
108
|
# This function should only be called from an EC2 instance otherwise the http
|
94
109
|
# call will fail.
|
95
110
|
##
|
96
|
-
def
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
111
|
+
def get_role_from_ec2_metadata(region)
|
112
|
+
begin
|
113
|
+
# instance_profile_arn = get_instance_profile_arn
|
114
|
+
# account_id = get_account_id_from_principal_arn(instance_profile_arn)
|
115
|
+
sts_client = Aws::STS::Client.new
|
116
|
+
account_id = sts_client.get_caller_identity().account
|
117
|
+
role_name = get_iam_role_name
|
118
|
+
aws_region = region.nil? ? get_region_from_az(get_availability_zone): region
|
119
|
+
|
120
|
+
iam_role_arn = "arn:aws:iam::#{account_id}:role/#{role_name}"
|
121
|
+
|
122
|
+
LOGGER.debug("IAM Principal ARN: #{iam_role_arn}")
|
123
|
+
LOGGER.debug("AWS Region: #{aws_region}")
|
124
|
+
|
125
|
+
return CerberusAuthInfo.new(iam_role_arn, aws_region, nil)
|
126
|
+
rescue Cerberus::Exception::HttpError
|
127
|
+
LOGGER.error("Failed to get instance IAM role infor from metadata service")
|
128
|
+
return nil
|
129
|
+
end
|
108
130
|
end
|
109
131
|
|
110
132
|
##
|
111
133
|
# Reach out to the Cerberus management service and get an auth token
|
112
134
|
##
|
113
|
-
def
|
135
|
+
def get_credentials_from_cerberus
|
136
|
+
LOGGER.debug("Authenticating with instance IAM role...")
|
114
137
|
begin
|
115
|
-
authData =
|
138
|
+
authData = authenticate_with_cerberus(@cerberus_auth_info.iam_principal_arn, @cerberus_auth_info.region)
|
116
139
|
|
117
140
|
LOGGER.debug("Got auth data from Cerberus. Attempting to decrypt...")
|
118
141
|
|
119
|
-
# decrypt the data we got from cerberus to get the
|
120
|
-
kms = Aws::KMS::Client.new(region: @
|
142
|
+
# decrypt the data we got from cerberus to get the cerberus token
|
143
|
+
kms = Aws::KMS::Client.new(region: @cerberus_auth_info.region)
|
121
144
|
|
122
145
|
decryptedAuthDataJson = JSON.parse(kms.decrypt(ciphertext_blob: Base64.decode64(authData)).plaintext)
|
123
146
|
|
@@ -135,19 +158,11 @@ module Cerberus
|
|
135
158
|
end
|
136
159
|
end
|
137
160
|
|
138
|
-
##
|
139
|
-
# Get the AWS account ID from the role ARN
|
140
|
-
# Expects formatting [some value]:[some value]:[some value]::[account id]
|
141
|
-
##
|
142
|
-
def getAccountIdFromRoleARN(roleARN)
|
143
|
-
roleARN.split(':')[ROLE_ARN_ARRAY_INDEX_OF_ACCOUNTNUM]
|
144
|
-
end
|
145
|
-
|
146
161
|
##
|
147
162
|
# Get the role name from EC@ Metadata
|
148
163
|
##
|
149
|
-
def
|
150
|
-
response =
|
164
|
+
def get_iam_role_name
|
165
|
+
response = call_ec2_metadata_service(IAM_ROLE_NAME_REL_URI)
|
151
166
|
response.body
|
152
167
|
end
|
153
168
|
|
@@ -155,23 +170,31 @@ module Cerberus
|
|
155
170
|
# Read the IAM role ARN from the instance metadata
|
156
171
|
# Will throw an HTTP exception if there is no IAM role associated with the instance
|
157
172
|
##
|
158
|
-
def
|
159
|
-
response =
|
160
|
-
|
161
|
-
|
173
|
+
def get_instance_profile_arn
|
174
|
+
response = call_ec2_metadata_service(EC2_INSTNACE_PROFILE_REL_URI)
|
175
|
+
json_response_body = JSON.parse(response.body)
|
176
|
+
json_response_body[EC2_INSTANCE_PROFILE_ARN_KEY]
|
177
|
+
end
|
178
|
+
|
179
|
+
##
|
180
|
+
# Get the AWS account ID from the role ARN
|
181
|
+
# Expects formatting [some value]:[some value]:[some value]::[account id]
|
182
|
+
##
|
183
|
+
def get_account_id_from_principal_arn(principal_arn)
|
184
|
+
principal_arn.split(':')[ROLE_ARN_ARRAY_INDEX_OF_ACCOUNT_NUM]
|
162
185
|
end
|
163
186
|
|
164
187
|
##
|
165
188
|
# Get the region from AWS instance metadata
|
166
189
|
##
|
167
|
-
def
|
168
|
-
|
190
|
+
def get_availability_zone
|
191
|
+
call_ec2_metadata_service(REGION_REL_URI).body
|
169
192
|
end
|
170
193
|
|
171
194
|
##
|
172
195
|
# Get region from AZ
|
173
196
|
##
|
174
|
-
def
|
197
|
+
def get_region_from_az(az)
|
175
198
|
az[0, az.length-1]
|
176
199
|
end
|
177
200
|
|
@@ -179,22 +202,22 @@ module Cerberus
|
|
179
202
|
# Call the instance metadata service with a relative URI and return the response if the call succeeds
|
180
203
|
# else throw an IOError for non-2xx responses and RuntimeError for any exceptions down the stack
|
181
204
|
##
|
182
|
-
def
|
183
|
-
url = URI(
|
184
|
-
|
205
|
+
def call_ec2_metadata_service(relative_uri)
|
206
|
+
url = URI(@instance_metadata_url + relative_uri)
|
207
|
+
CerberusUtils::Http.new.make_http_call(url, 'GET', false)
|
185
208
|
end
|
186
209
|
|
187
210
|
##
|
188
211
|
#
|
189
212
|
##
|
190
|
-
def
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
213
|
+
def authenticate_with_cerberus(iam_principal_arn, region)
|
214
|
+
post_json_data = JSON.generate({:iam_principal_arn => iam_principal_arn, :region => region})
|
215
|
+
auth_url = URI(@cerberus_base_url + ROLE_AUTH_REL_URI)
|
216
|
+
use_ssl = ! @cerberus_base_url.include?("localhost")
|
217
|
+
auth_response = CerberusUtils::Http.new.make_http_call(auth_url, 'POST', use_ssl, post_json_data)
|
195
218
|
# if we got this far, we should have a valid response with encrypted data
|
196
219
|
# send back the encrypted data
|
197
|
-
JSON.parse(
|
220
|
+
JSON.parse(auth_response.body)['auth_data']
|
198
221
|
end
|
199
222
|
|
200
223
|
end
|