cerberus_client 1.5.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build](https://travis-ci.org/Nike-Inc/cerberus-ruby-client.svg?branch=master)](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
|