cfn-model 0.4.30 → 0.5.3

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
  SHA256:
3
- metadata.gz: 4cfb10903ae3fbf18dc0c8d07db826851348197984e10834dde228a4f40d453d
4
- data.tar.gz: 9c2e9362dd9e75eb6ee7b90566468b579d5d725fbd23e1791f1af46926af4f8a
3
+ metadata.gz: 527a99ce24b15e3dc115ecfc163034c4a01599632b0ef0afb94ae9a232058d35
4
+ data.tar.gz: 3b230f07cad2a3369745a60684acbca599b6241fced5cb8c2c986e24304af258
5
5
  SHA512:
6
- metadata.gz: 6e5d026023c580e5ace9123da3ecf62cfd265b424c584c83156e5ee595c3ed863a2d783d971bc8f4b3fb3629007aa795156257ad75ea6a1a6084723ec64005b3
7
- data.tar.gz: 7798edc1e68b7073e3e880e4f66687c3f8e80bb3a5740f397a6af060d8d9fafa1b9912ad5393e71da26d0b65ff0d2b38da7418bf27e73474b33e33b0cf87c38b
6
+ metadata.gz: a4569f552dbc5c8776daa0f82277c25ec1d2af7c7e00814574b9031bf4daf318e956b40add9297e0a45c6dcb12401603c8b84260ef7d6fbdcd1d98bee31b9158
7
+ data.tar.gz: e8d001e530ccaeb57e05d9bcada2c914f7ef311e71912e9ac03bf5846c61389ed1d2b73c38a4ebdfb2e59a907b968b7d22d5f67b88c5dc7045bdd92b977fe0ec
@@ -9,6 +9,10 @@ require 'cfn-model/parser/parser_error'
9
9
  # references yet... in the meantime pile things up here and hope a pattern becomes
10
10
  # clear
11
11
  module References
12
+ def self.unsupported_passthru?(value)
13
+ value.has_key?('Fn::GetAtt') || value.has_key?('Fn::ImportValue') || value.has_key?('Fn::Transform') || value.has_key?('Fn::Cidr')
14
+ end
15
+
12
16
  def self.resolve_value(cfn_model, value)
13
17
  if value.is_a? Hash
14
18
  if value.has_key?('Ref')
@@ -17,7 +21,21 @@ module References
17
21
  resolve_map(cfn_model, value)
18
22
  elsif value.has_key?('Fn::If')
19
23
  resolve_if(cfn_model, value)
20
- else
24
+ elsif value.has_key?('Fn::Sub')
25
+ resolve_sub(cfn_model, value)
26
+ elsif value.has_key?('Fn::GetAZs')
27
+ resolve_getazs(cfn_model, value)
28
+ elsif value.has_key?('Fn::Split')
29
+ resolve_split(cfn_model, value)
30
+ elsif value.has_key?('Fn::Join')
31
+ resolve_join(cfn_model, value)
32
+ elsif value.has_key?('Fn::Base64')
33
+ resolve_base64(cfn_model, value)
34
+ elsif value.has_key?('Fn::Select')
35
+ resolve_select(cfn_model, value)
36
+ elsif unsupported_passthru?(value)
37
+ value
38
+ else # another mapping
21
39
  value.map do |k,v|
22
40
  [k, resolve_value(cfn_model, v)]
23
41
  end.to_h
@@ -101,6 +119,107 @@ module References
101
119
 
102
120
  private
103
121
 
122
+ def self.resolve_sub(cfn_model, expression)
123
+ if expression['Fn::Sub'].is_a? String
124
+ resolve_shorthand_sub(cfn_model, expression)
125
+ elsif expression['Fn::Sub'].is_a? Array
126
+ resolve_longform_sub(cfn_model, expression)
127
+ else
128
+ expression
129
+ end
130
+ end
131
+
132
+ def self.resolve_select(cfn_model, reference)
133
+ index = reference['Fn::Select'][0]
134
+ collection = References.resolve_value(cfn_model, reference['Fn::Select'][1])
135
+ if collection.is_a? Array
136
+ collection[index]
137
+ else
138
+ reference
139
+ end
140
+ end
141
+
142
+ def self.resolve_base64(cfn_model, reference)
143
+ References.resolve_value(cfn_model, reference['Fn::Base64'])
144
+ end
145
+
146
+ def self.resolve_join(cfn_model, reference)
147
+ delimiter = reference['Fn::Join'][0]
148
+ items = References.resolve_value(cfn_model, reference['Fn::Join'][1])
149
+ return reference unless items.is_a?(Array)
150
+ items.join(delimiter)
151
+ end
152
+
153
+ def self.resolve_split(cfn_model, reference)
154
+ delimiter = reference['Fn::Split'][0]
155
+ target_string = References.resolve_value(cfn_model, reference['Fn::Split'][1])
156
+ return reference unless target_string.is_a?(String)
157
+ target_string.split(delimiter)
158
+ end
159
+
160
+ def self.resolve_getazs(cfn_model, reference)
161
+ number_azs = References.resolve_value(cfn_model, { 'Ref' => 'AWS::NumberAZs' })
162
+ region = reference['Fn::GetAZs']
163
+ if region == '' || region == { 'Ref' => 'AWS::Region' }
164
+ region = References.resolve_value(cfn_model, { 'Ref' => 'AWS::Region' })
165
+ end
166
+ (('a'.ord)..('a'.ord+number_azs)).map do |az_number|
167
+ "#{region}#{az_number.chr}"
168
+ end
169
+ end
170
+
171
+ def self.strip_cfn_interpolation(reference)
172
+ reference[2..-2]
173
+ end
174
+
175
+ def self.references_in_sub(string_value)
176
+ # ignore ${!foo} as cfn interprets that as the literal ${foo}
177
+ references = string_value.scan /\${[^!].*?}/
178
+ references.map { |reference| strip_cfn_interpolation(reference) }
179
+ end
180
+
181
+ def self.resolvable_reference?(cfn_model, reference)
182
+ resolved_value = References.resolve_value(cfn_model, {'Ref'=>reference})
183
+ resolved_value != {'Ref'=>reference}
184
+ end
185
+
186
+ def self.resolve_shorthand_sub(cfn_model, expression)
187
+ string_value = expression['Fn::Sub']
188
+ subbed_string_value = string_value
189
+ has_unresolved_references = false
190
+ references_in_sub(string_value).each do |reference|
191
+ if resolvable_reference?(cfn_model, reference)
192
+ subbed_string_value = subbed_string_value.gsub(
193
+ "${#{reference}}",
194
+ References.resolve_value(cfn_model, {'Ref'=>reference})
195
+ )
196
+ end
197
+ end
198
+ subbed_string_value
199
+ end
200
+
201
+ def self.resolve_longform_sub(cfn_model, expression)
202
+ array_value = expression['Fn::Sub']
203
+ subbed_string_value = array_value[0]
204
+ substitution_mapping = array_value[1]
205
+ references_in_sub(subbed_string_value).each do |reference|
206
+ if substitution_mapping.has_key? reference
207
+ if References.resolve_value(cfn_model, substitution_mapping[reference]).is_a?(String)
208
+ subbed_string_value = subbed_string_value.gsub(
209
+ "${#{reference}}",
210
+ References.resolve_value(cfn_model, substitution_mapping[reference])
211
+ )
212
+ end
213
+ elsif resolvable_reference?(cfn_model, reference)
214
+ subbed_string_value = subbed_string_value.gsub(
215
+ "${#{reference}}",
216
+ References.resolve_value(cfn_model, {'Ref'=>reference})
217
+ )
218
+ end
219
+ end
220
+ subbed_string_value
221
+ end
222
+
104
223
  def self.resolve_if(cfn_model, expression)
105
224
  if_expression = expression['Fn::If']
106
225
  condition_name = if_expression[0]
@@ -44,6 +44,9 @@ class CfnParser
44
44
 
45
45
  apply_parameter_values(cfn_model, parameter_values_json)
46
46
 
47
+ # pass 2: tie together separate resources only where necessary to make life easier for rule logic
48
+ post_process_resource_model_elements cfn_model
49
+
47
50
  cfn_model
48
51
  end
49
52
 
@@ -87,8 +90,7 @@ class CfnParser
87
90
  transform_hash_into_parameters cfn_hash, cfn_model
88
91
  transform_hash_into_globals cfn_hash, cfn_model
89
92
 
90
- # pass 2: tie together separate resources only where necessary to make life easier for rule logic
91
- post_process_resource_model_elements cfn_model
93
+
92
94
 
93
95
  cfn_model
94
96
  end
@@ -279,7 +281,7 @@ class CfnParser
279
281
 
280
282
  def map_non_aws_resource_name_to_class_name(module_names)
281
283
  # this is a little hacky. we've been ignoring Custom so more for
282
- # backward compat. for Alexa and other transformed resources just jam the whole
284
+ # backward compat. for Alexa and other transformed resources just jam the whole
283
285
  # thing together
284
286
  if module_names.first == 'Custom'
285
287
  first_module_index = 1
@@ -306,7 +308,8 @@ class CfnParser
306
308
  else
307
309
  custom_resource_class_name = map_non_aws_resource_name_to_class_name(module_names)
308
310
  begin
309
- resource_class = Object.const_get custom_resource_class_name
311
+ custom_class = Object.const_get custom_resource_class_name
312
+ resource_class = custom_class if custom_class.is_a?(ModelElement)
310
313
  rescue NameError
311
314
  Object.const_set(custom_resource_class_name, resource_class)
312
315
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'cfn-model/model/iam_role'
4
4
  require 'cfn-model/model/policy'
5
+ require 'cfn-model/model/references'
5
6
  require_relative 'policy_document_parser'
6
7
 
7
8
  class IamGroupParser
@@ -12,8 +13,8 @@ class IamGroupParser
12
13
  next unless policy.has_key? 'PolicyName'
13
14
 
14
15
  new_policy = Policy.new
15
- new_policy.policy_name = policy['PolicyName']
16
- new_policy.policy_document = PolicyDocumentParser.new.parse(policy['PolicyDocument'])
16
+ new_policy.policy_name = References.resolve_value(cfn_model, policy['PolicyName'])
17
+ new_policy.policy_document = PolicyDocumentParser.new.parse(cfn_model, policy['PolicyDocument'])
17
18
  new_policy
18
19
  end.reject { |policy| policy.nil? }
19
20
  iam_group
@@ -2,20 +2,21 @@
2
2
 
3
3
  require 'cfn-model/model/iam_role'
4
4
  require 'cfn-model/model/policy'
5
+ require 'cfn-model/model/references'
5
6
  require_relative 'policy_document_parser'
6
7
 
7
8
  class IamRoleParser
8
9
  def parse(cfn_model:, resource:)
9
10
  iam_role = resource
10
11
 
11
- iam_role.assume_role_policy_document = PolicyDocumentParser.new.parse(iam_role.assumeRolePolicyDocument)
12
+ iam_role.assume_role_policy_document = PolicyDocumentParser.new.parse(cfn_model, iam_role.assumeRolePolicyDocument)
12
13
 
13
14
  iam_role.policy_objects = iam_role.policies.map do |policy|
14
15
  next unless policy.has_key? 'PolicyName'
15
16
 
16
17
  new_policy = Policy.new
17
- new_policy.policy_name = policy['PolicyName']
18
- new_policy.policy_document = PolicyDocumentParser.new.parse(policy['PolicyDocument'])
18
+ new_policy.policy_name = References.resolve_value(cfn_model, policy['PolicyName'])
19
+ new_policy.policy_document = PolicyDocumentParser.new.parse(cfn_model, policy['PolicyDocument'])
19
20
  new_policy
20
21
  end.reject { |policy| policy.nil? }
21
22
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'cfn-model/model/policy_document'
4
4
  require 'cfn-model/model/policy'
5
+ require 'cfn-model/model/references'
5
6
  require_relative 'policy_document_parser'
6
7
 
7
8
  class IamUserParser
@@ -12,8 +13,8 @@ class IamUserParser
12
13
  next unless policy.has_key? 'PolicyName'
13
14
 
14
15
  new_policy = Policy.new
15
- new_policy.policy_name = policy['PolicyName']
16
- new_policy.policy_document = PolicyDocumentParser.new.parse(policy['PolicyDocument'])
16
+ new_policy.policy_name = References.resolve_value(cfn_model, policy['PolicyName'])
17
+ new_policy.policy_document = PolicyDocumentParser.new.parse(cfn_model, policy['PolicyDocument'])
17
18
  new_policy
18
19
  end.reject { |policy| policy.nil? }
19
20
 
@@ -22,8 +23,8 @@ class IamUserParser
22
23
  user_to_group_additions = cfn_model.resources_by_type 'AWS::IAM::UserToGroupAddition'
23
24
  user_to_group_additions.each do |user_to_group_addition|
24
25
 
25
- if user_to_group_addition_has_username(user_to_group_addition.users,iam_user)
26
- iam_user.group_names << user_to_group_addition.groupName
26
+ if user_to_group_addition_has_username(user_to_group_addition.users, iam_user)
27
+ iam_user.group_names << References.resolve_value(cfn_model, user_to_group_addition.groupName)
27
28
 
28
29
  # we need to figure out the story on resolving Refs i think for this to be real
29
30
  end
@@ -9,7 +9,7 @@ class KmsKeyParser
9
9
  kms_key = resource
10
10
 
11
11
  new_policy = Policy.new
12
- new_policy.policy_document = PolicyDocumentParser.new.parse(kms_key.keyPolicy)
12
+ new_policy.policy_document = PolicyDocumentParser.new.parse(cfn_model, kms_key.keyPolicy)
13
13
  kms_key.key_policy = new_policy
14
14
 
15
15
  kms_key
@@ -43,7 +43,8 @@ class ParameterSubstitution
43
43
  'AWS::AccountId' => '111111111111',
44
44
  'AWS::Region' => 'us-east-1',
45
45
  'AWS::StackId' => 'arn:aws:cloudformation:us-east-1:111111111111:stack/stackname/51af3dc0-da77-11e4-872e-1234567db123',
46
- 'AWS::StackName' => 'stackname'
46
+ 'AWS::StackName' => 'stackname',
47
+ 'AWS::NumberAZs' => 2
47
48
  }
48
49
  pseudo_function_defaults.each do |function_name, default_value|
49
50
  parameter = Parameter.new
@@ -1,16 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'cfn-model/model/iam_policy'
4
+ require 'cfn-model/model/references'
5
+
4
6
  require 'cfn-model/model/policy_document'
5
7
 
6
8
  class PolicyDocumentParser
7
- def parse(raw_policy_document)
9
+ def parse(cfn_model, raw_policy_document)
8
10
  policy_document = PolicyDocument.new
9
11
 
10
- policy_document.version = raw_policy_document['Version']
12
+ policy_document.version = References.resolve_value(cfn_model, raw_policy_document['Version'])
11
13
 
12
14
  policy_document.statements = streamline_array(raw_policy_document['Statement']) do |statement|
13
- parse_statement statement
15
+ parse_statement cfn_model, statement
14
16
  end
15
17
 
16
18
  policy_document
@@ -18,17 +20,17 @@ class PolicyDocumentParser
18
20
 
19
21
  private
20
22
 
21
- def parse_statement(raw_statement)
23
+ def parse_statement(cfn_model, raw_statement)
22
24
  statement = Statement.new
23
- statement.effect = raw_statement['Effect']
24
- statement.sid = raw_statement['Sid']
25
- statement.condition = raw_statement['Condition']
26
- statement.actions = streamline_array(raw_statement['Action'])
27
- statement.not_actions = streamline_array(raw_statement['NotAction'])
28
- statement.resources = streamline_array(raw_statement['Resource'])
29
- statement.not_resources = streamline_array(raw_statement['NotResource'])
30
- statement.principal = raw_statement['Principal']
31
- statement.not_principal = raw_statement['NotPrincipal']
25
+ statement.effect = References.resolve_value(cfn_model, raw_statement['Effect'])
26
+ statement.sid = References.resolve_value(cfn_model, raw_statement['Sid'])
27
+ statement.condition = References.resolve_value(cfn_model, raw_statement['Condition'])
28
+ statement.actions = References.resolve_value(cfn_model, streamline_array(raw_statement['Action']))
29
+ statement.not_actions = References.resolve_value(cfn_model, streamline_array(raw_statement['NotAction']))
30
+ statement.resources = References.resolve_value(cfn_model, streamline_array(raw_statement['Resource']))
31
+ statement.not_resources = References.resolve_value(cfn_model, streamline_array(raw_statement['NotResource']))
32
+ statement.principal = References.resolve_value(cfn_model, raw_statement['Principal'])
33
+ statement.not_principal = References.resolve_value(cfn_model, raw_statement['NotPrincipal'])
32
34
  statement
33
35
  end
34
36
 
@@ -38,7 +38,7 @@ class SecurityGroupParser
38
38
  ingress_object = AWS::EC2::SecurityGroupIngress.new cfn_model
39
39
  ingress.each do |k, v|
40
40
  silently_fail do
41
- ingress_object.send("#{initialLower(k)}=", v)
41
+ ingress_object.send("#{initialLower(k)}=", References.resolve_value(cfn_model, v))
42
42
  mapped_at_least_one_attribute = true
43
43
  end
44
44
  end
@@ -59,7 +59,7 @@ class SecurityGroupParser
59
59
  egress.each do |k, v|
60
60
  next if k.match /::/
61
61
  silently_fail do
62
- egress_object.send("#{initialLower(k)}=", v)
62
+ egress_object.send("#{initialLower(k)}=", References.resolve_value(cfn_model, v))
63
63
  mapped_at_least_one_attribute = true
64
64
  end
65
65
 
@@ -6,7 +6,7 @@ require_relative 'policy_document_parser'
6
6
 
7
7
  class WithPolicyDocumentParser
8
8
  def parse(cfn_model:, resource:)
9
- resource.policy_document = PolicyDocumentParser.new.parse(resource.policyDocument)
9
+ resource.policy_document = PolicyDocumentParser.new.parse(cfn_model, resource.policyDocument)
10
10
  resource
11
11
  end
12
12
  end
@@ -112,8 +112,14 @@ class CfnModel
112
112
  resource_name,
113
113
  with_line_numbers)
114
114
 
115
- cfn_hash['Resources'][resource_name] = lambda_function lambda_fn_params
116
-
115
+ cfn_hash['Resources'][resource_name] = lambda_function(
116
+ handler: lambda_fn_params[:handler],
117
+ code_bucket: lambda_fn_params[:code_bucket],
118
+ code_key: lambda_fn_params[:code_key],
119
+ role: lambda_fn_params[:role],
120
+ runtime: lambda_fn_params[:runtime],
121
+ with_line_numbers: lambda_fn_params[:with_line_numbers]
122
+ )
117
123
  unless serverless_function['Properties']['Role']
118
124
  cfn_hash['Resources'][resource_name + 'Role'] = function_role(serverless_function,
119
125
  resource_name,
@@ -122,6 +128,13 @@ class CfnModel
122
128
 
123
129
  transform_function_events(cfn_hash, serverless_function, resource_name, with_line_numbers) if \
124
130
  serverless_function['Properties']['Events']
131
+
132
+ # Handle passing along cfn-nag specific metadata. SAM itself does not support metadata during transformation.
133
+ # https://github.com/aws/serverless-application-model/issues/264
134
+ if serverless_function.key?('Metadata') && serverless_function['Metadata'].key?('cfn_nag')
135
+ cfn_hash['Resources'][resource_name]['Metadata'] = serverless_function['Metadata']
136
+ cfn_hash['Resources'][resource_name + 'Role']['Metadata'] = serverless_function['Metadata']
137
+ end
125
138
  end
126
139
 
127
140
  def lambda_service_can_assume_role
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfn-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.30
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Kascic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-04 00:00:00.000000000 Z
11
+ date: 2020-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -165,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
167
  requirements: []
168
- rubygems_version: 3.1.2
168
+ rubygems_version: 3.1.4
169
169
  signing_key:
170
170
  specification_version: 4
171
171
  summary: cfn-model