miasma-aws 0.3.10 → 0.3.12
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 +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
|