stack_master 1.17.1-x64-mingw32 → 1.18.0-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
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: