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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 118dcd91cc2b5b9ae3c7dc9911fb59714c8562af
4
- data.tar.gz: 46c2fc8ba4d25944d958b243c8b7e298ca39e53c
3
+ metadata.gz: d3532d5cf6c91f4980b7f7f8173a93e39a6aa4ed
4
+ data.tar.gz: e19a8b88d91d49f20a6f351c9f071c0074e3b5bf
5
5
  SHA512:
6
- metadata.gz: a0b5bc48a5b7fd17d7f379a5852f33dcddcd5fae2350f72bb17c1320bb3711c38f192399e8768dae66b221da7d13e61569903be244b4b5e102ccaee281955a4b
7
- data.tar.gz: d13c6bcc6c47c28547e49e06768fa155f2d6d39e3a5f53dd0305d250f7bb8293c261436a9307f00203573771da022133c5fe6c69a9200ad650d86ed9c8f76c85
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 Alternative to the alternative secret store is accessing 1password secrets using the 1password cli (`op`).
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
- validate_parameter_value!(key, parameter_value)
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
- resolver_name = parameter_value.keys.first.to_s
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 = parameter_value.values.first
67
+ value = resolver_hash.values.first
58
68
  resolver_class_name = resolver_name.camelize
59
- call_resolver(resolver_class_name, value)
60
- rescue Aws::CloudFormation::Errors::ValidationError
61
- raise InvalidParameter, $!.message
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
- @decrypt_ejson_file ||= EJSONWrapper.decrypt(ejson_file_path,
31
- use_kms: @stack_definition.ejson_file_kms,
32
- region: ejson_file_region)
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
- filters = @ami_finder.build_filters_from_hash(value.fetch('filters'))
15
- @ami_finder.find_latest_ami(filters, owners).try(:image_id)
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
- @cf ||= StackMaster.cloud_formation_driver
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 = stack_key(stack_name, unaliased_region)
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
- return cf if cf.region == region
62
+ driver_key = "#{region}:#{credentials_key}"
67
63
 
68
- @cf_drivers.fetch(region) do
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[region] = cloud_formation_driver
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
@@ -1,3 +1,3 @@
1
1
  module StackMaster
2
- VERSION = "1.17.1"
2
+ VERSION = "1.18.0"
3
3
  end
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.17.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-10-03 00:00:00.000000000 Z
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://github.com/envato/stack_master
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: