miasma-aws 0.3.10 → 0.3.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +4 -0
- data/lib/miasma-aws.rb +2 -2
- data/lib/miasma-aws/api.rb +3 -3
- data/lib/miasma-aws/api/iam.rb +16 -17
- data/lib/miasma-aws/api/sts.rb +29 -30
- data/lib/miasma-aws/version.rb +1 -1
- data/lib/miasma/contrib/aws.rb +170 -185
- data/lib/miasma/contrib/aws/auto_scale.rb +23 -25
- data/lib/miasma/contrib/aws/compute.rb +51 -56
- data/lib/miasma/contrib/aws/load_balancer.rb +64 -70
- data/lib/miasma/contrib/aws/orchestration.rb +153 -159
- data/lib/miasma/contrib/aws/storage.rb +109 -113
- data/miasma-aws.gemspec +3 -2
- metadata +33 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 136015ad26078a11d453df779c7116887ca4198d5f1c7901d5f3f20a9dcb1bf3
|
4
|
+
data.tar.gz: 149a662fb85e2dacd67427b92192732af6031a7ab23f893f351a093360403d30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 300f29b1dacc60e70f6da191d32844a539e000e7fe9754dd07424b4b832dd1201ee2750768b40efd0aca6dbfe072fe94fcc0442b19492e98f6a777d5fd65eb17
|
7
|
+
data.tar.gz: e7b1c007d3d54b4147d83c3ac0975f2c26fe1593067e4c7d1a8a52ea697644f1351de7e6fc10583cecc8d651b59cb130804246ebd1a2e95fc81d317c7991787d
|
data/CHANGELOG.md
CHANGED
data/lib/miasma-aws.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "miasma"
|
2
|
+
require "miasma-aws/version"
|
data/lib/miasma-aws/api.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
require
|
1
|
+
require "miasma"
|
2
2
|
|
3
3
|
module Miasma
|
4
4
|
module Contrib
|
5
5
|
module Aws
|
6
6
|
# AWS API helpers
|
7
7
|
module Api
|
8
|
-
autoload :Sts,
|
9
|
-
autoload :Iam,
|
8
|
+
autoload :Sts, "miasma-aws/api/sts"
|
9
|
+
autoload :Iam, "miasma-aws/api/iam"
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
data/lib/miasma-aws/api/iam.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "miasma"
|
2
2
|
|
3
3
|
module Miasma
|
4
4
|
module Contrib
|
@@ -8,9 +8,9 @@ module Miasma
|
|
8
8
|
class Iam < Miasma::Types::Api
|
9
9
|
|
10
10
|
# Service name of the API
|
11
|
-
API_SERVICE =
|
11
|
+
API_SERVICE = "iam".freeze
|
12
12
|
# Supported version of the IAM API
|
13
|
-
API_VERSION =
|
13
|
+
API_VERSION = "2010-05-08".freeze
|
14
14
|
|
15
15
|
include Contrib::AwsApiCore::ApiCommon
|
16
16
|
include Contrib::AwsApiCore::RequestUtils
|
@@ -20,29 +20,28 @@ module Miasma
|
|
20
20
|
service_name = self.class::API_SERVICE.downcase
|
21
21
|
self.aws_host = [
|
22
22
|
service_name,
|
23
|
-
api_endpoint
|
24
|
-
].join(
|
23
|
+
api_endpoint,
|
24
|
+
].join(".")
|
25
25
|
end
|
26
26
|
|
27
27
|
# Fetch current user information
|
28
28
|
def user_info
|
29
29
|
result = request(
|
30
|
-
:path =>
|
30
|
+
:path => "/",
|
31
31
|
:params => {
|
32
|
-
|
33
|
-
}
|
34
|
-
).get(:body,
|
32
|
+
"Action" => "GetUser",
|
33
|
+
},
|
34
|
+
).get(:body, "GetUserResponse", "GetUserResult", "User")
|
35
35
|
Smash.new(
|
36
|
-
:user_id => result[
|
37
|
-
:path => result[
|
38
|
-
:username => result[
|
39
|
-
:arn => result[
|
40
|
-
:created => result[
|
41
|
-
:password_last_used => result[
|
42
|
-
:account_id => result[
|
36
|
+
:user_id => result["UserId"],
|
37
|
+
:path => result["Path"],
|
38
|
+
:username => result["UserName"],
|
39
|
+
:arn => result["Arn"],
|
40
|
+
:created => result["CreateDate"],
|
41
|
+
:password_last_used => result["PasswordLastUsed"],
|
42
|
+
:account_id => result["Arn"].split(":")[4],
|
43
43
|
)
|
44
44
|
end
|
45
|
-
|
46
45
|
end
|
47
46
|
end
|
48
47
|
end
|
data/lib/miasma-aws/api/sts.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "miasma"
|
2
2
|
|
3
3
|
module Miasma
|
4
4
|
module Contrib
|
@@ -8,9 +8,9 @@ module Miasma
|
|
8
8
|
class Sts < Miasma::Types::Api
|
9
9
|
|
10
10
|
# Service name of the API
|
11
|
-
API_SERVICE =
|
11
|
+
API_SERVICE = "sts".freeze
|
12
12
|
# Supported version of the STS API
|
13
|
-
API_VERSION =
|
13
|
+
API_VERSION = "2011-06-15".freeze
|
14
14
|
|
15
15
|
include Contrib::AwsApiCore::ApiCommon
|
16
16
|
include Contrib::AwsApiCore::RequestUtils
|
@@ -22,22 +22,22 @@ module Miasma
|
|
22
22
|
# @option args [Integer] :duration life of session in seconds
|
23
23
|
# @option args [String] :mfa_serial MFA device identification number
|
24
24
|
# @return [Hash]
|
25
|
-
def mfa_session(token_code, args={})
|
25
|
+
def mfa_session(token_code, args = {})
|
26
26
|
req_params = Smash.new.tap do |params|
|
27
|
-
params[
|
28
|
-
params[
|
29
|
-
params[
|
30
|
-
params[
|
27
|
+
params["Action"] = "GetSessionToken"
|
28
|
+
params["TokenCode"] = token_code.respond_to?(:call) ? token_code.call : token_code
|
29
|
+
params["DurationSeconds"] = args[:duration] if args[:duration]
|
30
|
+
params["SerialNumber"] = args[:mfa_serial].to_s.empty? ? default_mfa_serial : args[:mfa_serial]
|
31
31
|
end
|
32
32
|
result = request(
|
33
|
-
:path =>
|
34
|
-
:params => req_params
|
35
|
-
).get(:body,
|
33
|
+
:path => "/",
|
34
|
+
:params => req_params,
|
35
|
+
).get(:body, "GetSessionTokenResponse", "GetSessionTokenResult", "Credentials")
|
36
36
|
Smash.new(
|
37
|
-
:aws_sts_session_token => result[
|
38
|
-
:aws_sts_session_secret_access_key => result[
|
39
|
-
:aws_sts_session_access_key_id => result[
|
40
|
-
:aws_sts_session_token_expires => Time.parse(result[
|
37
|
+
:aws_sts_session_token => result["SessionToken"],
|
38
|
+
:aws_sts_session_secret_access_key => result["SecretAccessKey"],
|
39
|
+
:aws_sts_session_access_key_id => result["AccessKeyId"],
|
40
|
+
:aws_sts_session_token_expires => Time.parse(result["Expiration"]),
|
41
41
|
)
|
42
42
|
end
|
43
43
|
|
@@ -48,24 +48,24 @@ module Miasma
|
|
48
48
|
# @option args [String] :external_id
|
49
49
|
# @option args [String] :session_name
|
50
50
|
# @return [Hash]
|
51
|
-
def assume_role(role_arn, args={})
|
51
|
+
def assume_role(role_arn, args = {})
|
52
52
|
req_params = Smash.new.tap do |params|
|
53
|
-
params[
|
54
|
-
params[
|
55
|
-
params[
|
56
|
-
params[
|
53
|
+
params["Action"] = "AssumeRole"
|
54
|
+
params["RoleArn"] = role_arn
|
55
|
+
params["RoleSessionName"] = args[:session_name] || SecureRandom.uuid.tr("-", "")
|
56
|
+
params["ExternalId"] = args[:external_id] if args[:external_id]
|
57
57
|
end
|
58
58
|
result = request(
|
59
|
-
:path =>
|
60
|
-
:params => req_params
|
61
|
-
).get(:body,
|
59
|
+
:path => "/",
|
60
|
+
:params => req_params,
|
61
|
+
).get(:body, "AssumeRoleResponse", "AssumeRoleResult")
|
62
62
|
Smash.new(
|
63
|
-
:aws_sts_token => result.get(
|
64
|
-
:aws_sts_secret_access_key => result.get(
|
65
|
-
:aws_sts_access_key_id => result.get(
|
66
|
-
:aws_sts_token_expires => Time.parse(result.get(
|
67
|
-
:aws_sts_assumed_role_arn => result.get(
|
68
|
-
:aws_sts_assumed_role_id => result.get(
|
63
|
+
:aws_sts_token => result.get("Credentials", "SessionToken"),
|
64
|
+
:aws_sts_secret_access_key => result.get("Credentials", "SecretAccessKey"),
|
65
|
+
:aws_sts_access_key_id => result.get("Credentials", "AccessKeyId"),
|
66
|
+
:aws_sts_token_expires => Time.parse(result.get("Credentials", "Expiration")),
|
67
|
+
:aws_sts_assumed_role_arn => result.get("AssumedRoleUser", "Arn"),
|
68
|
+
:aws_sts_assumed_role_id => result.get("AssumedRoleUser", "AssumedRoleId"),
|
69
69
|
)
|
70
70
|
end
|
71
71
|
|
@@ -80,7 +80,6 @@ module Miasma
|
|
80
80
|
).user_info
|
81
81
|
"arn:aws:iam::#{user_data[:account_id]}:mfa/#{user_data[:username]}"
|
82
82
|
end
|
83
|
-
|
84
83
|
end
|
85
84
|
end
|
86
85
|
end
|
data/lib/miasma-aws/version.rb
CHANGED
data/lib/miasma/contrib/aws.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "miasma"
|
2
|
+
require "miasma/utils/smash"
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
4
|
+
require "time"
|
5
|
+
require "openssl"
|
6
6
|
|
7
7
|
# Miasma
|
8
8
|
module Miasma
|
9
9
|
module Contrib
|
10
10
|
# AWS API implementations
|
11
11
|
module Aws
|
12
|
-
autoload :Api,
|
12
|
+
autoload :Api, "miasma-aws/api"
|
13
13
|
end
|
14
|
+
|
14
15
|
# Core API for AWS access
|
15
16
|
class AwsApiCore
|
16
17
|
|
@@ -27,31 +28,30 @@ module Miasma
|
|
27
28
|
# @return [Array]
|
28
29
|
def all_result_pages(next_token, *result_key, &block)
|
29
30
|
list = []
|
30
|
-
options = next_token ? Smash.new(
|
31
|
+
options = next_token ? Smash.new("NextToken" => next_token) : Smash.new
|
31
32
|
result = block.call(options)
|
32
33
|
content = result.get(*result_key.dup)
|
33
|
-
if
|
34
|
+
if content.is_a?(Array)
|
34
35
|
list += content
|
35
36
|
else
|
36
37
|
list << content
|
37
38
|
end
|
38
39
|
set = result.get(*result_key.slice(0, 3))
|
39
|
-
if
|
40
|
+
if set.is_a?(Hash) && set["NextToken"]
|
40
41
|
[content].flatten.compact.each do |item|
|
41
|
-
if
|
42
|
-
item[
|
42
|
+
if item.is_a?(Hash)
|
43
|
+
item["NextToken"] = set["NextToken"]
|
43
44
|
end
|
44
45
|
end
|
45
|
-
list += all_result_pages(set[
|
46
|
+
list += all_result_pages(set["NextToken"], *result_key, &block)
|
46
47
|
end
|
47
48
|
list.compact
|
48
49
|
end
|
49
|
-
|
50
50
|
end
|
51
51
|
|
52
52
|
# @return [String] current time ISO8601 format
|
53
53
|
def self.time_iso8601
|
54
|
-
Time.now.utc.strftime(
|
54
|
+
Time.now.utc.strftime("%Y%m%dT%H%M%SZ")
|
55
55
|
end
|
56
56
|
|
57
57
|
# HMAC helper class
|
@@ -93,7 +93,7 @@ module Miasma
|
|
93
93
|
# @param data [String]
|
94
94
|
# @param key_override [Object]
|
95
95
|
# @return [Object] signature
|
96
|
-
def sign(data, key_override=nil)
|
96
|
+
def sign(data, key_override = nil)
|
97
97
|
result = OpenSSL::HMAC.digest(digest, key_override || key, data)
|
98
98
|
digest.reset
|
99
99
|
result
|
@@ -104,12 +104,11 @@ module Miasma
|
|
104
104
|
# @param data [String]
|
105
105
|
# @param key_override [Object]
|
106
106
|
# @return [String] hex encoded signature
|
107
|
-
def hex_sign(data, key_override=nil)
|
107
|
+
def hex_sign(data, key_override = nil)
|
108
108
|
result = OpenSSL::HMAC.hexdigest(digest, key_override || key, data)
|
109
109
|
digest.reset
|
110
110
|
result
|
111
111
|
end
|
112
|
-
|
113
112
|
end
|
114
113
|
|
115
114
|
# Base signature class
|
@@ -117,7 +116,7 @@ module Miasma
|
|
117
116
|
|
118
117
|
# Create new instance
|
119
118
|
def initialize(*args)
|
120
|
-
raise NotImplementedError.new
|
119
|
+
raise NotImplementedError.new "This class should not be used directly!"
|
121
120
|
end
|
122
121
|
|
123
122
|
# Generate the signature
|
@@ -126,7 +125,7 @@ module Miasma
|
|
126
125
|
# @param path [String] request path
|
127
126
|
# @param opts [Hash] request options
|
128
127
|
# @return [String] signature
|
129
|
-
def generate(http_method, path, opts={})
|
128
|
+
def generate(http_method, path, opts = {})
|
130
129
|
raise NotImplementedError
|
131
130
|
end
|
132
131
|
|
@@ -136,10 +135,9 @@ module Miasma
|
|
136
135
|
# @return [String] escaped string
|
137
136
|
def safe_escape(string)
|
138
137
|
string.to_s.gsub(/([^a-zA-Z0-9_.\-~])/) do |match|
|
139
|
-
|
138
|
+
"%" << match.unpack("H2" * match.bytesize).join("%").upcase
|
140
139
|
end
|
141
140
|
end
|
142
|
-
|
143
141
|
end
|
144
142
|
|
145
143
|
# AWS signature version 4
|
@@ -162,7 +160,7 @@ module Miasma
|
|
162
160
|
# @param service [String]
|
163
161
|
# @return [self]
|
164
162
|
def initialize(access_key, secret_key, region, service)
|
165
|
-
@hmac = Hmac.new(
|
163
|
+
@hmac = Hmac.new("sha256", secret_key)
|
166
164
|
@access_key = access_key
|
167
165
|
@region = region
|
168
166
|
@service = service
|
@@ -177,7 +175,7 @@ module Miasma
|
|
177
175
|
def generate(http_method, path, opts)
|
178
176
|
signature = generate_signature(http_method, path, opts)
|
179
177
|
"#{algorithm} Credential=#{access_key}/#{credential_scope}, " \
|
180
|
-
|
178
|
+
"SignedHeaders=#{signed_headers(opts[:headers])}, Signature=#{signature}"
|
181
179
|
end
|
182
180
|
|
183
181
|
# Generate URL with signed params
|
@@ -189,17 +187,17 @@ module Miasma
|
|
189
187
|
def generate_url(http_method, path, opts)
|
190
188
|
opts[:params].merge!(
|
191
189
|
Smash.new(
|
192
|
-
|
193
|
-
|
194
|
-
|
190
|
+
"X-Amz-SignedHeaders" => signed_headers(opts[:headers]),
|
191
|
+
"X-Amz-Algorithm" => algorithm,
|
192
|
+
"X-Amz-Credential" => "#{access_key}/#{credential_scope}",
|
195
193
|
)
|
196
194
|
)
|
197
195
|
signature = generate_signature(
|
198
196
|
http_method, path,
|
199
|
-
opts.merge(:body =>
|
197
|
+
opts.merge(:body => "UNSIGNED-PAYLOAD")
|
200
198
|
)
|
201
|
-
params = opts[:params].merge(
|
202
|
-
"https://#{opts[:headers][
|
199
|
+
params = opts[:params].merge("X-Amz-Signature" => signature)
|
200
|
+
"https://#{opts[:headers]["Host"]}/#{path}?#{canonical_query(params)}"
|
203
201
|
end
|
204
202
|
|
205
203
|
# Generate the signature
|
@@ -211,11 +209,11 @@ module Miasma
|
|
211
209
|
def generate_signature(http_method, path, opts)
|
212
210
|
to_sign = [
|
213
211
|
algorithm,
|
214
|
-
opts.to_smash.fetch(:headers,
|
212
|
+
opts.to_smash.fetch(:headers, "X-Amz-Date", AwsApiCore.time_iso8601),
|
215
213
|
credential_scope,
|
216
214
|
hashed_canonical_request(
|
217
215
|
can_req = build_canonical_request(http_method, path, opts)
|
218
|
-
)
|
216
|
+
),
|
219
217
|
].join("\n")
|
220
218
|
signature = sign_request(to_sign)
|
221
219
|
end
|
@@ -226,13 +224,13 @@ module Miasma
|
|
226
224
|
# @return [String] signature
|
227
225
|
def sign_request(request)
|
228
226
|
key = hmac.sign(
|
229
|
-
|
227
|
+
"aws4_request",
|
230
228
|
hmac.sign(
|
231
229
|
service,
|
232
230
|
hmac.sign(
|
233
231
|
region,
|
234
232
|
hmac.sign(
|
235
|
-
Time.now.utc.strftime(
|
233
|
+
Time.now.utc.strftime("%Y%m%d"),
|
236
234
|
"AWS4#{hmac.key}"
|
237
235
|
)
|
238
236
|
)
|
@@ -243,17 +241,17 @@ module Miasma
|
|
243
241
|
|
244
242
|
# @return [String] signature algorithm
|
245
243
|
def algorithm
|
246
|
-
|
244
|
+
"AWS4-HMAC-SHA256"
|
247
245
|
end
|
248
246
|
|
249
247
|
# @return [String] credential scope for request
|
250
248
|
def credential_scope
|
251
249
|
[
|
252
|
-
Time.now.utc.strftime(
|
250
|
+
Time.now.utc.strftime("%Y%m%d"),
|
253
251
|
region,
|
254
252
|
service,
|
255
|
-
|
256
|
-
].join(
|
253
|
+
"aws4_request",
|
254
|
+
].join("/")
|
257
255
|
end
|
258
256
|
|
259
257
|
# Generate the hash of the canonical request
|
@@ -271,7 +269,7 @@ module Miasma
|
|
271
269
|
# @param opts [Hash] request options
|
272
270
|
# @return [String] canonical request string
|
273
271
|
def build_canonical_request(http_method, path, opts)
|
274
|
-
unless
|
272
|
+
unless path.start_with?("/")
|
275
273
|
path = "/#{path}"
|
276
274
|
end
|
277
275
|
[
|
@@ -280,7 +278,7 @@ module Miasma
|
|
280
278
|
canonical_query(opts[:params]),
|
281
279
|
canonical_headers(opts[:headers]),
|
282
280
|
signed_headers(opts[:headers]),
|
283
|
-
canonical_payload(opts)
|
281
|
+
canonical_payload(opts),
|
284
282
|
].join("\n")
|
285
283
|
end
|
286
284
|
|
@@ -293,7 +291,7 @@ module Miasma
|
|
293
291
|
params = Hash[params.sort_by(&:first)]
|
294
292
|
query = params.map do |key, value|
|
295
293
|
"#{safe_escape(key)}=#{safe_escape(value)}"
|
296
|
-
end.join(
|
294
|
+
end.join("&")
|
297
295
|
end
|
298
296
|
|
299
297
|
# Build the canonical header string used for signing
|
@@ -304,7 +302,7 @@ module Miasma
|
|
304
302
|
headers ||= {}
|
305
303
|
headers = Hash[headers.sort_by(&:first)]
|
306
304
|
headers.map do |key, value|
|
307
|
-
[key.downcase, value.chomp].join(
|
305
|
+
[key.downcase, value.chomp].join(":")
|
308
306
|
end.join("\n") << "\n"
|
309
307
|
end
|
310
308
|
|
@@ -315,7 +313,7 @@ module Miasma
|
|
315
313
|
def signed_headers(headers)
|
316
314
|
headers ||= {}
|
317
315
|
headers.sort_by(&:first).map(&:first).
|
318
|
-
map(&:downcase).join(
|
316
|
+
map(&:downcase).join(";")
|
319
317
|
end
|
320
318
|
|
321
319
|
# Build the canonical payload string used for signing
|
@@ -323,76 +321,75 @@ module Miasma
|
|
323
321
|
# @param options [Hash] request options
|
324
322
|
# @return [String] body checksum
|
325
323
|
def canonical_payload(options)
|
326
|
-
body = options.fetch(:body,
|
327
|
-
if
|
324
|
+
body = options.fetch(:body, "")
|
325
|
+
if options[:json]
|
328
326
|
body = MultiJson.dump(options[:json])
|
329
|
-
elsif
|
327
|
+
elsif options[:form]
|
330
328
|
body = URI.encode_www_form(options[:form])
|
331
329
|
end
|
332
|
-
if
|
330
|
+
if body == "UNSIGNED-PAYLOAD"
|
333
331
|
body
|
334
332
|
else
|
335
333
|
hmac.hexdigest_of(body)
|
336
334
|
end
|
337
335
|
end
|
338
|
-
|
339
336
|
end
|
340
337
|
|
341
338
|
# Common API setup
|
342
339
|
module ApiCommon
|
343
|
-
|
344
340
|
def self.included(klass)
|
345
341
|
klass.class_eval do
|
346
|
-
attribute :aws_profile_name, [FalseClass, String], :default =>
|
342
|
+
attribute :aws_profile_name, [FalseClass, String], :default => ENV.fetch("AWS_PROFILE", "default")
|
347
343
|
attribute :aws_sts_token, String
|
348
344
|
attribute :aws_sts_role_arn, String
|
349
345
|
attribute :aws_sts_external_id, String
|
350
346
|
attribute :aws_sts_role_session_name, String
|
351
347
|
attribute :aws_sts_region, String
|
352
348
|
attribute :aws_sts_host, String
|
353
|
-
attribute :aws_sts_session_token, String
|
349
|
+
attribute :aws_sts_session_token, String, :default => ENV["AWS_SESSION_TOKEN"]
|
354
350
|
attribute :aws_sts_session_token_code, [String, Proc, Method]
|
355
351
|
attribute :aws_sts_mfa_serial_number, [String]
|
356
|
-
attribute :aws_credentials_file, String,
|
357
|
-
:
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
352
|
+
attribute :aws_credentials_file, String,
|
353
|
+
:required => true,
|
354
|
+
:default => ENV.fetch("AWS_SHARED_CREDENTIALS_FILE", File.join(Dir.home, ".aws/credentials"))
|
355
|
+
attribute :aws_config_file, String,
|
356
|
+
:required => true,
|
357
|
+
:default => ENV.fetch("AWS_CONFIG_FILE", File.join(Dir.home, ".aws/config"))
|
358
|
+
attribute :aws_access_key_id, String, :required => true, :default => ENV["AWS_ACCESS_KEY_ID"]
|
359
|
+
attribute :aws_secret_access_key, String, :required => true, :default => ENV["AWS_SECRET_ACCESS_KEY"]
|
362
360
|
attribute :aws_iam_instance_profile, [TrueClass, FalseClass], :default => false
|
363
361
|
attribute :aws_ecs_task_profile, [TrueClass, FalseClass], :default => false
|
364
|
-
attribute :aws_region, String, :required => true
|
362
|
+
attribute :aws_region, String, :required => true, :default => ENV["AWS_DEFAULT_REGION"]
|
365
363
|
attribute :aws_host, String
|
366
364
|
attribute :aws_bucket_region, String
|
367
|
-
attribute :api_endpoint, String, :required => true, :default =>
|
365
|
+
attribute :api_endpoint, String, :required => true, :default => "amazonaws.com"
|
368
366
|
attribute :euca_compat, Symbol, :allowed_values => [:path, :dns],
|
369
|
-
|
370
|
-
attribute :euca_dns_map, Smash, :coerce => lambda{|v| v.to_smash},
|
371
|
-
|
367
|
+
:coerce => lambda { |v| v.is_a?(String) ? v.to_sym : v }
|
368
|
+
attribute :euca_dns_map, Smash, :coerce => lambda { |v| v.to_smash },
|
369
|
+
:default => Smash.new
|
372
370
|
attribute :ssl_enabled, [TrueClass, FalseClass], :default => true
|
373
371
|
end
|
374
372
|
|
375
373
|
# AWS config file key remapping
|
376
374
|
klass.const_set(:CONFIG_FILE_REMAP,
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
)
|
384
|
-
klass.const_set(:INSTANCE_PROFILE_HOST, 'http://169.254.169.254'.freeze)
|
375
|
+
Smash.new(
|
376
|
+
"region" => "aws_region",
|
377
|
+
"role_arn" => "aws_sts_role_arn",
|
378
|
+
"aws_security_token" => "aws_sts_token",
|
379
|
+
"aws_session_token" => "aws_sts_session_token",
|
380
|
+
).to_smash.freeze)
|
381
|
+
klass.const_set(:INSTANCE_PROFILE_HOST, "http://169.254.169.254".freeze)
|
385
382
|
klass.const_set(
|
386
383
|
:INSTANCE_PROFILE_PATH,
|
387
|
-
|
384
|
+
"latest/meta-data/iam/security-credentials".freeze
|
388
385
|
)
|
389
386
|
klass.const_set(
|
390
387
|
:INSTANCE_PROFILE_AZ_PATH,
|
391
|
-
|
388
|
+
"latest/meta-data/placement/availability-zone".freeze
|
392
389
|
)
|
393
|
-
klass.const_set(:ECS_TASK_PROFILE_HOST,
|
390
|
+
klass.const_set(:ECS_TASK_PROFILE_HOST, "http://169.254.170.2".freeze)
|
394
391
|
klass.const_set(
|
395
|
-
:ECS_TASK_PROFILE_PATH, ENV[
|
392
|
+
:ECS_TASK_PROFILE_PATH, ENV["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"]
|
396
393
|
)
|
397
394
|
end
|
398
395
|
|
@@ -408,7 +405,7 @@ module Miasma
|
|
408
405
|
Smash.new(
|
409
406
|
:type => type,
|
410
407
|
:provider => provider,
|
411
|
-
:credentials => creds
|
408
|
+
:credentials => creds,
|
412
409
|
)
|
413
410
|
)
|
414
411
|
end
|
@@ -420,20 +417,24 @@ module Miasma
|
|
420
417
|
# @param creds [Hash]
|
421
418
|
# @return [TrueClass]
|
422
419
|
def custom_setup(creds)
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
420
|
+
cred_file = load_aws_file(aws_credentials_file)
|
421
|
+
config_file = load_aws_file(aws_config_file)
|
422
|
+
file_creds = Smash.new.tap do |fc|
|
423
|
+
(config_file.keys + cred_file.keys).uniq.reverse.each do |k|
|
424
|
+
fc[k] = config_file.fetch(k, Smash.new).merge(
|
425
|
+
cred_file.fetch(k, Smash.new)
|
426
|
+
)
|
427
|
+
end
|
428
|
+
end
|
429
|
+
profile = creds[:aws_profile_name]
|
430
|
+
new_creds = Smash.new
|
431
|
+
while profile
|
432
|
+
new_creds = file_creds.fetch(profile, Smash.new).merge(new_creds)
|
433
|
+
profile = new_creds.delete(:source_profile)
|
435
434
|
end
|
436
|
-
|
435
|
+
new_creds = file_creds.fetch(:default, Smash.new).merge(new_creds)
|
436
|
+
creds.replace(new_creds.merge(creds))
|
437
|
+
if creds[:aws_iam_instance_profile]
|
437
438
|
self.class.const_get(:ECS_TASK_PROFILE_PATH).nil? ?
|
438
439
|
load_instance_credentials!(creds) :
|
439
440
|
load_ecs_credentials!(creds)
|
@@ -450,7 +451,7 @@ module Miasma
|
|
450
451
|
skip = self.class.attributes.keys.map(&:to_s)
|
451
452
|
creds.each do |k, v|
|
452
453
|
k = k.to_s
|
453
|
-
if
|
454
|
+
if k.start_with?("aws_") && !skip.include?(k)
|
454
455
|
data[k] = v
|
455
456
|
end
|
456
457
|
end
|
@@ -465,17 +466,17 @@ module Miasma
|
|
465
466
|
[
|
466
467
|
self.class.const_get(:INSTANCE_PROFILE_HOST),
|
467
468
|
self.class.const_get(:INSTANCE_PROFILE_PATH),
|
468
|
-
|
469
|
-
].join(
|
469
|
+
"",
|
470
|
+
].join("/")
|
470
471
|
).body.to_s.strip
|
471
472
|
data = HTTP.get(
|
472
473
|
[
|
473
474
|
self.class.const_get(:INSTANCE_PROFILE_HOST),
|
474
475
|
self.class.const_get(:INSTANCE_PROFILE_PATH),
|
475
|
-
role
|
476
|
-
].join(
|
476
|
+
role,
|
477
|
+
].join("/")
|
477
478
|
).body
|
478
|
-
unless
|
479
|
+
unless data.is_a?(Hash)
|
479
480
|
begin
|
480
481
|
data = MultiJson.load(data.to_s)
|
481
482
|
rescue MultiJson::ParseError
|
@@ -483,7 +484,7 @@ module Miasma
|
|
483
484
|
end
|
484
485
|
end
|
485
486
|
creds.merge!(extract_creds(data))
|
486
|
-
unless
|
487
|
+
unless creds[:aws_region]
|
487
488
|
creds[:aws_region] = get_region
|
488
489
|
end
|
489
490
|
true
|
@@ -501,10 +502,10 @@ module Miasma
|
|
501
502
|
data = HTTP.get(
|
502
503
|
[
|
503
504
|
self.class.const_get(:ECS_TASK_PROFILE_HOST),
|
504
|
-
self.class.const_get(:ECS_TASK_PROFILE_PATH)
|
505
|
+
self.class.const_get(:ECS_TASK_PROFILE_PATH),
|
505
506
|
].join
|
506
507
|
).body
|
507
|
-
unless
|
508
|
+
unless data.is_a?(Hash)
|
508
509
|
begin
|
509
510
|
data = MultiJson.load(data.to_s)
|
510
511
|
rescue MultiJson::ParseError
|
@@ -512,7 +513,7 @@ module Miasma
|
|
512
513
|
end
|
513
514
|
end
|
514
515
|
creds.merge!(extract_creds(data))
|
515
|
-
unless
|
516
|
+
unless creds[:aws_region]
|
516
517
|
creds[:aws_region] = get_region
|
517
518
|
end
|
518
519
|
true
|
@@ -524,11 +525,11 @@ module Miasma
|
|
524
525
|
# @return [Hash]
|
525
526
|
def extract_creds(data)
|
526
527
|
c = Smash.new
|
527
|
-
c[:aws_access_key_id] = data[
|
528
|
-
c[:aws_secret_access_key] = data[
|
529
|
-
c[:aws_sts_token] = data[
|
530
|
-
c[:aws_sts_token_expires] = Time.xmlschema(data[
|
531
|
-
c[:aws_sts_role_arn] = data[
|
528
|
+
c[:aws_access_key_id] = data["AccessKeyId"]
|
529
|
+
c[:aws_secret_access_key] = data["SecretAccessKey"]
|
530
|
+
c[:aws_sts_token] = data["Token"]
|
531
|
+
c[:aws_sts_token_expires] = Time.xmlschema(data["Expiration"])
|
532
|
+
c[:aws_sts_role_arn] = data["RoleArn"] # used in ECS Role but not instance role
|
532
533
|
c
|
533
534
|
end
|
534
535
|
|
@@ -539,30 +540,30 @@ module Miasma
|
|
539
540
|
az = HTTP.get(
|
540
541
|
[
|
541
542
|
self.class.const_get(:INSTANCE_PROFILE_HOST),
|
542
|
-
self.class.const_get(:INSTANCE_PROFILE_AZ_PATH)
|
543
|
-
].join(
|
543
|
+
self.class.const_get(:INSTANCE_PROFILE_AZ_PATH),
|
544
|
+
].join("/")
|
544
545
|
).body.to_s.strip
|
545
|
-
az.sub!(/[a-zA-Z]+$/,
|
546
|
+
az.sub!(/[a-zA-Z]+$/, "")
|
546
547
|
az
|
547
548
|
end
|
548
549
|
|
549
550
|
def sts_mfa_session!(creds)
|
550
|
-
if
|
551
|
+
if sts_mfa_session_update_required?(creds)
|
551
552
|
sts = Miasma::Contrib::Aws::Api::Sts.new(
|
552
553
|
:aws_access_key_id => creds[:aws_access_key_id],
|
553
554
|
:aws_secret_access_key => creds[:aws_secret_access_key],
|
554
|
-
:aws_region => creds.fetch(:aws_sts_region,
|
555
|
+
:aws_region => creds.fetch(:aws_sts_region, "us-east-1"),
|
555
556
|
:aws_credentials_file => creds.fetch(
|
556
557
|
:aws_credentials_file, aws_credentials_file
|
557
558
|
),
|
558
559
|
:aws_config_file => creds.fetch(:aws_config_file, aws_config_file),
|
559
560
|
:aws_profile_name => creds[:aws_profile_name],
|
560
|
-
:aws_host => creds[:aws_sts_host]
|
561
|
+
:aws_host => creds[:aws_sts_host],
|
561
562
|
)
|
562
563
|
creds.merge!(
|
563
564
|
sts.mfa_session(
|
564
565
|
creds[:aws_sts_session_token_code],
|
565
|
-
:mfa_serial => creds[:aws_sts_mfa_serial_number]
|
566
|
+
:mfa_serial => creds[:aws_sts_mfa_serial_number],
|
566
567
|
)
|
567
568
|
)
|
568
569
|
end
|
@@ -574,22 +575,22 @@ module Miasma
|
|
574
575
|
# @param creds [Hash]
|
575
576
|
# @return [TrueClass]
|
576
577
|
def sts_assume_role!(creds)
|
577
|
-
if
|
578
|
+
if sts_assume_role_update_required?(creds)
|
578
579
|
sts = Miasma::Contrib::Aws::Api::Sts.new(
|
579
580
|
:aws_access_key_id => get_credential(:access_key_id, creds),
|
580
581
|
:aws_secret_access_key => get_credential(:secret_access_key, creds),
|
581
|
-
:aws_region => creds.fetch(:aws_sts_region,
|
582
|
+
:aws_region => creds.fetch(:aws_sts_region, "us-east-1"),
|
582
583
|
:aws_credentials_file => creds.fetch(
|
583
584
|
:aws_credentials_file, aws_credentials_file
|
584
585
|
),
|
585
586
|
:aws_config_file => creds.fetch(:aws_config_file, aws_config_file),
|
586
587
|
:aws_host => creds[:aws_sts_host],
|
587
|
-
:aws_sts_token => creds[:aws_sts_session_token]
|
588
|
+
:aws_sts_token => creds[:aws_sts_session_token],
|
588
589
|
)
|
589
590
|
role_info = sts.assume_role(
|
590
591
|
creds[:aws_sts_role_arn],
|
591
592
|
:session_name => creds[:aws_sts_role_session_name],
|
592
|
-
:external_id => creds[:aws_sts_external_id]
|
593
|
+
:external_id => creds[:aws_sts_external_id],
|
593
594
|
)
|
594
595
|
creds.merge!(role_info)
|
595
596
|
end
|
@@ -599,38 +600,37 @@ module Miasma
|
|
599
600
|
# Load configuration from the AWS configuration file
|
600
601
|
#
|
601
602
|
# @param file_path [String] path to configuration file
|
602
|
-
# @param profile [String] name of profile to load
|
603
603
|
# @return [Smash]
|
604
|
-
def load_aws_file(file_path
|
605
|
-
if
|
606
|
-
|
607
|
-
key =
|
604
|
+
def load_aws_file(file_path)
|
605
|
+
if File.exist?(file_path)
|
606
|
+
Smash.new.tap do |creds|
|
607
|
+
key = :default
|
608
608
|
File.readlines(file_path).each_with_index do |line, idx|
|
609
609
|
line.strip!
|
610
|
-
next if line.empty? || line.start_with?(
|
611
|
-
if
|
612
|
-
unless
|
610
|
+
next if line.empty? || line.start_with?("#")
|
611
|
+
if line.start_with?("[")
|
612
|
+
unless line.end_with?("]")
|
613
613
|
raise ArgumentError.new(
|
614
614
|
"Failed to parse aws file! (#{file_path} line #{idx + 1})"
|
615
615
|
)
|
616
616
|
end
|
617
|
-
key = line.tr(
|
617
|
+
key = line.tr("[]", "").strip.sub(/^profile /, "")
|
618
618
|
creds[key] = Smash.new
|
619
619
|
else
|
620
|
-
unless
|
620
|
+
unless key
|
621
621
|
raise ArgumentError.new(
|
622
622
|
"Failed to parse aws file! (#{file_path} line #{idx + 1}) " \
|
623
|
-
|
623
|
+
"- No section defined!"
|
624
624
|
)
|
625
625
|
end
|
626
|
-
line_args = line.split(
|
626
|
+
line_args = line.split("=", 2).map(&:strip)
|
627
627
|
line_args.first.replace(
|
628
628
|
self.class.const_get(:CONFIG_FILE_REMAP).fetch(
|
629
629
|
line_args.first, line_args.first
|
630
630
|
)
|
631
631
|
)
|
632
|
-
if
|
633
|
-
unless
|
632
|
+
if line_args.last.start_with?('"')
|
633
|
+
unless line_args.last.end_with?('"')
|
634
634
|
raise ArgumentError.new(
|
635
635
|
"Failed to parse aws file! (#{file_path} line #{idx + 1})"
|
636
636
|
)
|
@@ -647,17 +647,6 @@ module Miasma
|
|
647
647
|
end
|
648
648
|
end
|
649
649
|
end
|
650
|
-
|
651
|
-
l_profile = l_config.fetch(profile, Smash.new)
|
652
|
-
l_source_profile = l_config.fetch(l_profile[:source_profile], Smash.new)
|
653
|
-
|
654
|
-
l_creds = l_config.fetch(
|
655
|
-
:default, Smash.new
|
656
|
-
).merge(
|
657
|
-
l_source_profile
|
658
|
-
).merge(
|
659
|
-
l_profile
|
660
|
-
)
|
661
650
|
else
|
662
651
|
Smash.new
|
663
652
|
end
|
@@ -665,33 +654,31 @@ module Miasma
|
|
665
654
|
|
666
655
|
# Setup for API connections
|
667
656
|
def connect
|
668
|
-
unless
|
669
|
-
if
|
670
|
-
service_name = (
|
671
|
-
self.class.const_defined?(:EUCA_API_SERVICE) ?
|
657
|
+
unless aws_host
|
658
|
+
if euca_compat
|
659
|
+
service_name = (self.class.const_defined?(:EUCA_API_SERVICE) ?
|
672
660
|
self.class::EUCA_API_SERVICE :
|
673
|
-
self.class::API_SERVICE
|
674
|
-
)
|
661
|
+
self.class::API_SERVICE)
|
675
662
|
else
|
676
663
|
service_name = self.class::API_SERVICE.downcase
|
677
664
|
end
|
678
|
-
if
|
665
|
+
if euca_compat == :path
|
679
666
|
self.aws_host = [
|
680
667
|
api_endpoint,
|
681
|
-
|
682
|
-
service_name
|
683
|
-
].join(
|
684
|
-
elsif
|
668
|
+
"services",
|
669
|
+
service_name,
|
670
|
+
].join("/")
|
671
|
+
elsif euca_compat == :dns && euca_dns_map[service_name]
|
685
672
|
self.aws_host = [
|
686
673
|
euca_dns_map[service_name],
|
687
|
-
api_endpoint
|
688
|
-
].join(
|
674
|
+
api_endpoint,
|
675
|
+
].join(".")
|
689
676
|
else
|
690
677
|
self.aws_host = [
|
691
678
|
service_name,
|
692
679
|
aws_region,
|
693
|
-
api_endpoint
|
694
|
-
].join(
|
680
|
+
api_endpoint,
|
681
|
+
].join(".")
|
695
682
|
end
|
696
683
|
end
|
697
684
|
end
|
@@ -710,11 +697,11 @@ module Miasma
|
|
710
697
|
#
|
711
698
|
# @param key [String, Symbol] credential suffix
|
712
699
|
# @return [Object]
|
713
|
-
def get_credential(key, data_hash=nil)
|
700
|
+
def get_credential(key, data_hash = nil)
|
714
701
|
data_hash = attributes if data_hash.nil?
|
715
|
-
if
|
702
|
+
if data_hash[:aws_sts_token]
|
716
703
|
data_hash.fetch("aws_sts_#{key}", data_hash["aws_#{key}"])
|
717
|
-
elsif
|
704
|
+
elsif data_hash[:aws_sts_session_token]
|
718
705
|
data_hash.fetch("aws_sts_session_#{key}", data_hash["aws_#{key}"])
|
719
706
|
else
|
720
707
|
data_hash["aws_#{key}"]
|
@@ -729,14 +716,14 @@ module Miasma
|
|
729
716
|
# @return [HTTP] connection for requests (forces headers)
|
730
717
|
def connection
|
731
718
|
super.headers(
|
732
|
-
|
733
|
-
|
719
|
+
"Host" => aws_host,
|
720
|
+
"X-Amz-Date" => Contrib::AwsApiCore.time_iso8601,
|
734
721
|
)
|
735
722
|
end
|
736
723
|
|
737
724
|
# @return [String] endpoint for request
|
738
725
|
def endpoint
|
739
|
-
"http#{
|
726
|
+
"http#{"s" if ssl_enabled}://#{aws_host}"
|
740
727
|
end
|
741
728
|
|
742
729
|
# Override to inject signature
|
@@ -751,41 +738,41 @@ module Miasma
|
|
751
738
|
options = options ? options.to_smash : Smash.new
|
752
739
|
options[:headers] = Smash[connection.default_options.headers.to_a].
|
753
740
|
merge(options.fetch(:headers, Smash.new))
|
754
|
-
if
|
755
|
-
if
|
756
|
-
options.set(:form,
|
741
|
+
if self.class::API_VERSION
|
742
|
+
if options[:form]
|
743
|
+
options.set(:form, "Version", self.class::API_VERSION)
|
757
744
|
else
|
758
745
|
options[:params] = options.fetch(
|
759
746
|
:params, Smash.new
|
760
747
|
).to_smash.deep_merge(
|
761
748
|
Smash.new(
|
762
|
-
|
749
|
+
"Version" => self.class::API_VERSION,
|
763
750
|
)
|
764
751
|
)
|
765
752
|
end
|
766
753
|
end
|
767
|
-
if
|
768
|
-
if
|
754
|
+
if aws_sts_session_token || aws_sts_session_token_code
|
755
|
+
if sts_mfa_session_update_required?
|
769
756
|
sts_mfa_session!(data)
|
770
757
|
end
|
771
|
-
options.set(:headers,
|
758
|
+
options.set(:headers, "X-Amz-Security-Token", aws_sts_session_token)
|
772
759
|
end
|
773
|
-
if
|
774
|
-
if
|
760
|
+
if aws_sts_token || aws_sts_role_arn
|
761
|
+
if sts_assume_role_update_required?
|
775
762
|
sts_assume_role!(data)
|
776
763
|
end
|
777
|
-
options.set(:headers,
|
764
|
+
options.set(:headers, "X-Amz-Security-Token", aws_sts_token)
|
778
765
|
end
|
779
766
|
signature = signer.generate(http_method, path, options)
|
780
767
|
update_request(connection, options)
|
781
|
-
options = Hash[options.map{|k, v| [k.to_sym, v] }]
|
768
|
+
options = Hash[options.map { |k, v| [k.to_sym, v] }]
|
782
769
|
connection.auth(signature).send(http_method, dest, options)
|
783
770
|
end
|
784
771
|
|
785
772
|
# @return [TrueClass, FalseClass]
|
786
773
|
# @note update check only applied if assuming role
|
787
|
-
def sts_assume_role_update_required?(args={})
|
788
|
-
if
|
774
|
+
def sts_assume_role_update_required?(args = {})
|
775
|
+
if args.fetch(:aws_sts_role_arn, attributes[:aws_sts_role_arn])
|
789
776
|
expiry = args.fetch(:aws_sts_token_expires, attributes[:aws_sts_token_expires])
|
790
777
|
expiry.nil? || expiry <= Time.now - 15
|
791
778
|
else
|
@@ -795,9 +782,9 @@ module Miasma
|
|
795
782
|
|
796
783
|
# @return [TrueClass, FalseClass]
|
797
784
|
# @note update check only applied if assuming role
|
798
|
-
def sts_mfa_session_update_required?(args={})
|
799
|
-
if(args.fetch(:aws_sts_session_token_code,
|
800
|
-
|
785
|
+
def sts_mfa_session_update_required?(args = {})
|
786
|
+
if (args.fetch(:aws_sts_session_token_code,
|
787
|
+
attributes[:aws_sts_session_token_code]))
|
801
788
|
expiry = args.fetch(
|
802
789
|
:aws_sts_session_token_expires,
|
803
790
|
attributes[:aws_sts_session_token_expires]
|
@@ -822,10 +809,10 @@ module Miasma
|
|
822
809
|
# @param exception [Exception]
|
823
810
|
# @return [TrueClass, FalseClass]
|
824
811
|
def perform_request_retry(exception)
|
825
|
-
if
|
826
|
-
if
|
827
|
-
if
|
828
|
-
exception.response.body.to_s.downcase.include?(
|
812
|
+
if exception.is_a?(Miasma::Error::ApiError)
|
813
|
+
if [400, 500, 503].include?(exception.response.code)
|
814
|
+
if exception.response.code == 400
|
815
|
+
exception.response.body.to_s.downcase.include?("throttl")
|
829
816
|
else
|
830
817
|
true
|
831
818
|
end
|
@@ -841,15 +828,13 @@ module Miasma
|
|
841
828
|
def retryable_allowed?(*_)
|
842
829
|
true
|
843
830
|
end
|
844
|
-
|
845
831
|
end
|
846
|
-
|
847
832
|
end
|
848
833
|
end
|
849
834
|
|
850
|
-
Models::Compute.autoload :Aws,
|
851
|
-
Models::LoadBalancer.autoload :Aws,
|
852
|
-
Models::AutoScale.autoload :Aws,
|
853
|
-
Models::Orchestration.autoload :Aws,
|
854
|
-
Models::Storage.autoload :Aws,
|
835
|
+
Models::Compute.autoload :Aws, "miasma/contrib/aws/compute"
|
836
|
+
Models::LoadBalancer.autoload :Aws, "miasma/contrib/aws/load_balancer"
|
837
|
+
Models::AutoScale.autoload :Aws, "miasma/contrib/aws/auto_scale"
|
838
|
+
Models::Orchestration.autoload :Aws, "miasma/contrib/aws/orchestration"
|
839
|
+
Models::Storage.autoload :Aws, "miasma/contrib/aws/storage"
|
855
840
|
end
|