stack_master 1.17.1-x64-mingw32 → 1.18.0-x64-mingw32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +42 -1
- data/lib/stack_master.rb +1 -0
- data/lib/stack_master/parameter_resolver.rb +31 -6
- data/lib/stack_master/parameter_resolvers/ejson.rb +11 -3
- data/lib/stack_master/parameter_resolvers/latest_ami.rb +3 -3
- data/lib/stack_master/parameter_resolvers/parameter_store.rb +1 -6
- data/lib/stack_master/parameter_resolvers/sns_topic_name.rb +0 -5
- data/lib/stack_master/parameter_resolvers/stack_output.rb +9 -9
- data/lib/stack_master/role_assumer.rb +54 -0
- data/lib/stack_master/version.rb +1 -1
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3532d5cf6c91f4980b7f7f8173a93e39a6aa4ed
|
4
|
+
data.tar.gz: e19a8b88d91d49f20a6f351c9f071c0074e3b5bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6cb5727e7d430164ab187159baf057bd42dcc423baf2dd84f2c9ea08ed7f2f6d1ad9926b62882f4cf401baa8ac0768a1dc1437518840c71cb26cfe5a007a817
|
7
|
+
data.tar.gz: 3726f1557c815827bd22f2eb1e40708b911b5e03174ecc45ee519be33ea6ce88541499d7a3bf0ecf31267379e9dfa19b223e64fc120e4ab604bac3b675ff452a
|
data/README.md
CHANGED
@@ -198,6 +198,47 @@ One benefit of using parameter resolvers instead of hard coding values like VPC
|
|
198
198
|
IDs and resource ARNs is that the same configuration works cross
|
199
199
|
region/account, even though the resolved values will be different.
|
200
200
|
|
201
|
+
### Cross-account parameter resolving
|
202
|
+
|
203
|
+
One way to resolve parameter values from different accounts to the one StackMaster runs in, is to
|
204
|
+
assume a role in another account with the relevant IAM permissions to execute successfully.
|
205
|
+
|
206
|
+
This is supported in StackMaster by specifying the `role` and `account` properties for the
|
207
|
+
parameter resolver in the stack's parameters file.
|
208
|
+
|
209
|
+
All parameter resolvers are supported.
|
210
|
+
|
211
|
+
```yaml
|
212
|
+
vpc_peering_id:
|
213
|
+
role: cross-account-parameter-resolver
|
214
|
+
account: 1234567890
|
215
|
+
stack_output: vpc-peering-stack-in-other-account/peering_name
|
216
|
+
|
217
|
+
an_array_param:
|
218
|
+
role: cross-account-parameter-resolver
|
219
|
+
account: 1234567890
|
220
|
+
stack_outputs:
|
221
|
+
- stack-in-account1/output
|
222
|
+
- stack-in-account1/another_output
|
223
|
+
|
224
|
+
another_array_param:
|
225
|
+
- role: cross-account-parameter-resolver
|
226
|
+
account: 1234567890
|
227
|
+
stack_output: stack-in-account1/output
|
228
|
+
- role: cross-account-parameter-resolver
|
229
|
+
account: 0987654321
|
230
|
+
stack_output: stack-in-account2/output
|
231
|
+
|
232
|
+
my_secret:
|
233
|
+
role: cross-account-parameter-resolver
|
234
|
+
account: 1234567890
|
235
|
+
parameter_store: ssm_parameter_name
|
236
|
+
```
|
237
|
+
|
238
|
+
An example of use case where cross-account parameter resolving is particularly useful is when
|
239
|
+
setting up VPC peering where you need the VPC ID of the peer. Without the ability to assume
|
240
|
+
a role in another account, the only option was to hard code the peer's VPC ID.
|
241
|
+
|
201
242
|
### Stack Output
|
202
243
|
|
203
244
|
The stack output parameter resolver looks up outputs from other stacks in the
|
@@ -270,7 +311,7 @@ db_password:
|
|
270
311
|
```
|
271
312
|
|
272
313
|
### 1Password Lookup
|
273
|
-
An
|
314
|
+
An alternative to the secrets store is accessing 1password secrets using the 1password cli (`op`).
|
274
315
|
You declare a 1password lookup with the following parameters in your parameters file:
|
275
316
|
|
276
317
|
```
|
data/lib/stack_master.rb
CHANGED
@@ -30,6 +30,7 @@ module StackMaster
|
|
30
30
|
autoload :SecurityGroupFinder, 'stack_master/security_group_finder'
|
31
31
|
autoload :ParameterLoader, 'stack_master/parameter_loader'
|
32
32
|
autoload :ParameterResolver, 'stack_master/parameter_resolver'
|
33
|
+
autoload :RoleAssumer, 'stack_master/role_assumer'
|
33
34
|
autoload :ResolverArray, 'stack_master/resolver_array'
|
34
35
|
autoload :Resolver, 'stack_master/resolver_array'
|
35
36
|
autoload :Utils, 'stack_master/utils'
|
@@ -49,16 +49,37 @@ module StackMaster
|
|
49
49
|
return parameter_value.to_s if Numeric === parameter_value || parameter_value == true || parameter_value == false
|
50
50
|
return resolve_array_parameter_values(key, parameter_value).join(',') if Array === parameter_value
|
51
51
|
return parameter_value unless Hash === parameter_value
|
52
|
-
|
52
|
+
resolve_parameter_resolver_hash(key, parameter_value)
|
53
|
+
rescue Aws::CloudFormation::Errors::ValidationError
|
54
|
+
raise InvalidParameter, $!.message
|
55
|
+
end
|
56
|
+
|
57
|
+
def resolve_parameter_resolver_hash(key, parameter_value)
|
58
|
+
# strip out account and role
|
59
|
+
resolver_hash = parameter_value.except('account', 'role')
|
60
|
+
account, role = parameter_value.values_at('account', 'role')
|
53
61
|
|
54
|
-
|
62
|
+
validate_parameter_value!(key, resolver_hash)
|
63
|
+
|
64
|
+
resolver_name = resolver_hash.keys.first.to_s
|
55
65
|
load_parameter_resolver(resolver_name)
|
56
66
|
|
57
|
-
value =
|
67
|
+
value = resolver_hash.values.first
|
58
68
|
resolver_class_name = resolver_name.camelize
|
59
|
-
|
60
|
-
|
61
|
-
|
69
|
+
|
70
|
+
assume_role_if_present(account, role, key) do
|
71
|
+
call_resolver(resolver_class_name, value)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def assume_role_if_present(account, role, key)
|
76
|
+
return yield if account.nil? && role.nil?
|
77
|
+
if account.nil? || role.nil?
|
78
|
+
raise InvalidParameter, "Both 'account' and 'role' are required to assume role for parameter '#{key}'"
|
79
|
+
end
|
80
|
+
role_assumer.assume_role(account, role) do
|
81
|
+
yield
|
82
|
+
end
|
62
83
|
end
|
63
84
|
|
64
85
|
def resolve_array_parameter_values(key, parameter_values)
|
@@ -94,5 +115,9 @@ module StackMaster
|
|
94
115
|
raise InvalidParameter, "#{key} hash contained more than one key: #{parameter_value.inspect}"
|
95
116
|
end
|
96
117
|
end
|
118
|
+
|
119
|
+
def role_assumer
|
120
|
+
@role_assumer ||= RoleAssumer.new
|
121
|
+
end
|
97
122
|
end
|
98
123
|
end
|
@@ -8,6 +8,7 @@ module StackMaster
|
|
8
8
|
def initialize(config, stack_definition)
|
9
9
|
@config = config
|
10
10
|
@stack_definition = stack_definition
|
11
|
+
@decrypted_ejson_files = {}
|
11
12
|
end
|
12
13
|
|
13
14
|
def resolve(secret_key)
|
@@ -27,9 +28,12 @@ module StackMaster
|
|
27
28
|
end
|
28
29
|
|
29
30
|
def decrypt_ejson_file
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
ejson_file_key = credentials_key
|
32
|
+
@decrypted_ejson_files.fetch(ejson_file_key) do
|
33
|
+
@decrypted_ejson_files[ejson_file_key] = EJSONWrapper.decrypt(ejson_file_path,
|
34
|
+
use_kms: @stack_definition.ejson_file_kms,
|
35
|
+
region: ejson_file_region)
|
36
|
+
end
|
33
37
|
end
|
34
38
|
|
35
39
|
def ejson_file_region
|
@@ -43,6 +47,10 @@ module StackMaster
|
|
43
47
|
def secret_path_relative_to_base
|
44
48
|
@secret_path_relative_to_base ||= File.join('secrets', @stack_definition.ejson_file)
|
45
49
|
end
|
50
|
+
|
51
|
+
def credentials_key
|
52
|
+
Aws.config[:credentials]&.object_id
|
53
|
+
end
|
46
54
|
end
|
47
55
|
end
|
48
56
|
end
|
@@ -6,13 +6,13 @@ module StackMaster
|
|
6
6
|
def initialize(config, stack_definition)
|
7
7
|
@config = config
|
8
8
|
@stack_definition = stack_definition
|
9
|
-
@ami_finder = AmiFinder.new(@stack_definition.region)
|
10
9
|
end
|
11
10
|
|
12
11
|
def resolve(value)
|
13
12
|
owners = Array(value.fetch('owners', 'self').to_s)
|
14
|
-
|
15
|
-
|
13
|
+
ami_finder = AmiFinder.new(@stack_definition.region)
|
14
|
+
filters = ami_finder.build_filters_from_hash(value.fetch('filters'))
|
15
|
+
ami_finder.find_latest_ami(filters, owners).try(:image_id)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
@@ -11,6 +11,7 @@ module StackMaster
|
|
11
11
|
|
12
12
|
def resolve(value)
|
13
13
|
begin
|
14
|
+
ssm = Aws::SSM::Client.new(region: @stack_definition.region)
|
14
15
|
resp = ssm.get_parameter(
|
15
16
|
name: value,
|
16
17
|
with_decryption: true
|
@@ -20,12 +21,6 @@ module StackMaster
|
|
20
21
|
end
|
21
22
|
resp.parameter.value
|
22
23
|
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def ssm
|
27
|
-
@ssm ||= Aws::SSM::Client.new(region: @stack_definition.region)
|
28
|
-
end
|
29
24
|
end
|
30
25
|
end
|
31
26
|
end
|
@@ -8,7 +8,6 @@ module StackMaster
|
|
8
8
|
def initialize(config, stack_definition)
|
9
9
|
@config = config
|
10
10
|
@stack_definition = stack_definition
|
11
|
-
@stacks = {}
|
12
11
|
end
|
13
12
|
|
14
13
|
def resolve(value)
|
@@ -19,10 +18,6 @@ module StackMaster
|
|
19
18
|
|
20
19
|
private
|
21
20
|
|
22
|
-
def cf
|
23
|
-
@cf ||= StackMaster.cloud_formation_driver
|
24
|
-
end
|
25
|
-
|
26
21
|
def sns_topic_finder
|
27
22
|
StackMaster::SnsTopicFinder.new(@stack_definition.region)
|
28
23
|
end
|
@@ -32,7 +32,7 @@ module StackMaster
|
|
32
32
|
private
|
33
33
|
|
34
34
|
def cf
|
35
|
-
|
35
|
+
StackMaster.cloud_formation_driver
|
36
36
|
end
|
37
37
|
|
38
38
|
def parse!(value)
|
@@ -49,7 +49,7 @@ module StackMaster
|
|
49
49
|
|
50
50
|
def find_stack(stack_name, region)
|
51
51
|
unaliased_region = @config.unalias_region(region)
|
52
|
-
stack_key =
|
52
|
+
stack_key = "#{unaliased_region}:#{stack_name}:#{credentials_key}"
|
53
53
|
|
54
54
|
@stacks.fetch(stack_key) do
|
55
55
|
regional_cf = cf_for_region(unaliased_region)
|
@@ -58,19 +58,19 @@ module StackMaster
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
def stack_key(stack_name, region)
|
62
|
-
"#{region}:#{stack_name}"
|
63
|
-
end
|
64
|
-
|
65
61
|
def cf_for_region(region)
|
66
|
-
|
62
|
+
driver_key = "#{region}:#{credentials_key}"
|
67
63
|
|
68
|
-
@cf_drivers.fetch(
|
64
|
+
@cf_drivers.fetch(driver_key) do
|
69
65
|
cloud_formation_driver = cf.class.new
|
70
66
|
cloud_formation_driver.set_region(region)
|
71
|
-
@cf_drivers[
|
67
|
+
@cf_drivers[driver_key] = cloud_formation_driver
|
72
68
|
end
|
73
69
|
end
|
70
|
+
|
71
|
+
def credentials_key
|
72
|
+
Aws.config[:credentials]&.object_id
|
73
|
+
end
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'active_support/core_ext/object/deep_dup'
|
2
|
+
|
3
|
+
module StackMaster
|
4
|
+
class RoleAssumer
|
5
|
+
BlockNotSpecified = Class.new(StandardError)
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@credentials = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def assume_role(account, role, &block)
|
12
|
+
raise BlockNotSpecified unless block_given?
|
13
|
+
raise ArgumentError, "Both 'account' and 'role' are required to assume a role" if account.nil? || role.nil?
|
14
|
+
|
15
|
+
role_credentials = assume_role_credentials(account, role)
|
16
|
+
with_temporary_credentials(role_credentials) do
|
17
|
+
with_temporary_cf_driver do
|
18
|
+
block.call
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def with_temporary_credentials(credentials, &block)
|
26
|
+
original_aws_config = Aws.config
|
27
|
+
Aws.config = original_aws_config.deep_dup
|
28
|
+
Aws.config[:credentials] = credentials
|
29
|
+
block.call
|
30
|
+
ensure
|
31
|
+
Aws.config = original_aws_config
|
32
|
+
end
|
33
|
+
|
34
|
+
def with_temporary_cf_driver(&block)
|
35
|
+
original_driver = StackMaster.cloud_formation_driver
|
36
|
+
new_driver = original_driver.class.new
|
37
|
+
new_driver.set_region(original_driver.region)
|
38
|
+
StackMaster.cloud_formation_driver = new_driver
|
39
|
+
block.call
|
40
|
+
ensure
|
41
|
+
StackMaster.cloud_formation_driver = original_driver
|
42
|
+
end
|
43
|
+
|
44
|
+
def assume_role_credentials(account, role)
|
45
|
+
credentials_key = "#{account}:#{role}"
|
46
|
+
@credentials.fetch(credentials_key) do
|
47
|
+
@credentials[credentials_key] = Aws::AssumeRoleCredentials.new(
|
48
|
+
role_arn: "arn:aws:iam::#{account}:role/#{role}",
|
49
|
+
role_session_name: "stack-master-role-assumer"
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/stack_master/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stack_master
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.18.0
|
5
5
|
platform: x64-mingw32
|
6
6
|
authors:
|
7
7
|
- Steve Hodgkiss
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-
|
12
|
+
date: 2019-12-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -468,6 +468,7 @@ files:
|
|
468
468
|
- lib/stack_master/parameter_resolvers/stack_output.rb
|
469
469
|
- lib/stack_master/prompter.rb
|
470
470
|
- lib/stack_master/resolver_array.rb
|
471
|
+
- lib/stack_master/role_assumer.rb
|
471
472
|
- lib/stack_master/security_group_finder.rb
|
472
473
|
- lib/stack_master/sns_topic_finder.rb
|
473
474
|
- lib/stack_master/sparkle_formation/compile_time/allowed_pattern_validator.rb
|
@@ -510,10 +511,14 @@ files:
|
|
510
511
|
- stacktemplates/parameter_stack_name.yml
|
511
512
|
- stacktemplates/stack.json.erb
|
512
513
|
- stacktemplates/stack_master.yml.erb
|
513
|
-
homepage: https://
|
514
|
+
homepage: https://opensource.envato.com/projects/stack_master.html
|
514
515
|
licenses:
|
515
516
|
- MIT
|
516
|
-
metadata:
|
517
|
+
metadata:
|
518
|
+
bug_tracker_uri: https://github.com/envato/stack_master/issues
|
519
|
+
changelog_uri: https://github.com/envato/stack_master/blob/master/CHANGELOG.md
|
520
|
+
documentation_uri: https://www.rubydoc.info/gems/stack_master/1.18.0
|
521
|
+
source_code_uri: https://github.com/envato/stack_master/tree/v1.18.0
|
517
522
|
post_install_message:
|
518
523
|
rdoc_options: []
|
519
524
|
require_paths:
|