cfn-nag 0.5.61 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/bin/spcm_scan +69 -0
  3. data/lib/cfn-nag/cfn_nag.rb +1 -0
  4. data/lib/cfn-nag/cfn_nag_config.rb +4 -1
  5. data/lib/cfn-nag/cfn_nag_executor.rb +22 -1
  6. data/lib/cfn-nag/cli_options.rb +18 -0
  7. data/lib/cfn-nag/custom_rule_loader.rb +23 -72
  8. data/lib/cfn-nag/custom_rules/AmazonMQBrokerUsersPasswordRule.rb +2 -2
  9. data/lib/cfn-nag/custom_rules/AmplifyAppAccessTokenRule.rb +2 -1
  10. data/lib/cfn-nag/custom_rules/AmplifyAppBasicAuthConfigPasswordRule.rb +2 -1
  11. data/lib/cfn-nag/custom_rules/AmplifyAppOauthTokenRule.rb +2 -1
  12. data/lib/cfn-nag/custom_rules/AmplifyBranchBasicAuthConfigPasswordRule.rb +2 -1
  13. data/lib/cfn-nag/custom_rules/ApiGatewayV2AccessLoggingRule.rb +1 -1
  14. data/lib/cfn-nag/custom_rules/AppStreamDirectoryConfigServiceAccountCredentialsAccountPasswordRule.rb +3 -2
  15. data/lib/cfn-nag/custom_rules/CodePipelineWebhookAuthenticationConfigurationSecretTokenRule.rb +2 -1
  16. data/lib/cfn-nag/custom_rules/DMSEndpointMongoDbSettingsPasswordRule.rb +2 -1
  17. data/lib/cfn-nag/custom_rules/DMSEndpointPasswordRule.rb +2 -1
  18. data/lib/cfn-nag/custom_rules/DirectoryServiceMicrosoftADPasswordRule.rb +2 -1
  19. data/lib/cfn-nag/custom_rules/DirectoryServiceSimpleADPasswordRule.rb +2 -1
  20. data/lib/cfn-nag/custom_rules/DocDBDBClusterMasterUserPasswordRule.rb +2 -1
  21. data/lib/cfn-nag/custom_rules/EMRClusterKerberosAttributesADDomainJoinPasswordRule.rb +2 -1
  22. data/lib/cfn-nag/custom_rules/EMRClusterKerberosAttributesCrossRealmTrustPrincipalPasswordRule.rb +3 -2
  23. data/lib/cfn-nag/custom_rules/EMRClusterKerberosAttributesKdcAdminPasswordRule.rb +2 -1
  24. data/lib/cfn-nag/custom_rules/ElastiCacheReplicationGroupAuthTokenRule.rb +2 -1
  25. data/lib/cfn-nag/custom_rules/IAMUserLoginProfilePasswordRule.rb +2 -1
  26. data/lib/cfn-nag/custom_rules/KinesisFirehoseDeliveryStreamRedshiftDestinationConfigurationPasswordRule.rb +3 -2
  27. data/lib/cfn-nag/custom_rules/KinesisFirehoseDeliveryStreamSplunkDestinationConfigurationHECTokenRule.rb +3 -2
  28. data/lib/cfn-nag/custom_rules/LambdaPermissionEventSourceTokenRule.rb +2 -1
  29. data/lib/cfn-nag/custom_rules/OpsWorksAppAppSourcePasswordRule.rb +2 -1
  30. data/lib/cfn-nag/custom_rules/OpsWorksAppSslConfigurationPrivateKeyRule.rb +2 -1
  31. data/lib/cfn-nag/custom_rules/OpsWorksStackCustomCookbooksSourcePasswordRule.rb +2 -1
  32. data/lib/cfn-nag/custom_rules/OpsWorksStackRdsDbInstancesDbPasswordRule.rb +3 -2
  33. data/lib/cfn-nag/custom_rules/PinpointAPNSChannelPrivateKeyRule.rb +2 -1
  34. data/lib/cfn-nag/custom_rules/PinpointAPNSChannelTokenKeyRule.rb +2 -1
  35. data/lib/cfn-nag/custom_rules/PinpointAPNSSandboxChannelPrivateKeyRule.rb +2 -1
  36. data/lib/cfn-nag/custom_rules/PinpointAPNSSandboxChannelTokenKeyRule.rb +2 -1
  37. data/lib/cfn-nag/custom_rules/PinpointAPNSVoipChannelPrivateKeyRule.rb +2 -1
  38. data/lib/cfn-nag/custom_rules/PinpointAPNSVoipChannelTokenKeyRule.rb +2 -1
  39. data/lib/cfn-nag/custom_rules/PinpointAPNSVoipSandboxChannelPrivateKeyRule.rb +2 -1
  40. data/lib/cfn-nag/custom_rules/PinpointAPNSVoipSandboxChannelTokenKeyRule.rb +2 -1
  41. data/lib/cfn-nag/custom_rules/RDSDBClusterMasterUserPasswordRule.rb +2 -1
  42. data/lib/cfn-nag/custom_rules/RDSDBInstanceMasterUserPasswordRule.rb +2 -1
  43. data/lib/cfn-nag/custom_rules/RDSDBInstanceMasterUsernameRule.rb +2 -1
  44. data/lib/cfn-nag/custom_rules/RedshiftClusterMasterUserPasswordRule.rb +2 -1
  45. data/lib/cfn-nag/custom_rules/SPCMRule.rb +66 -0
  46. data/lib/cfn-nag/custom_rules/SecretsManagerSecretKmsKeyIdRule.rb +4 -3
  47. data/lib/cfn-nag/iam_complexity_metric/condition_metric.rb +85 -0
  48. data/lib/cfn-nag/iam_complexity_metric/html_results_renderer.rb +45 -0
  49. data/lib/cfn-nag/iam_complexity_metric/policy_document_metric.rb +11 -0
  50. data/lib/cfn-nag/iam_complexity_metric/spcm.rb +79 -0
  51. data/lib/cfn-nag/iam_complexity_metric/statement_metric.rb +104 -0
  52. data/lib/cfn-nag/iam_complexity_metric/weights.rb +22 -0
  53. data/lib/cfn-nag/metadata.rb +78 -0
  54. metadata +15 -5
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HtmlRenderer
4
+ def render(results:)
5
+ output = '<html><body><table>'
6
+ results.each do |result|
7
+ output += '<tr><td><table><tr><td>'
8
+ output += result[:filename]
9
+ output += '</td></tr><tr><td>'
10
+ output += render_policy(result)
11
+ output += render_role(result)
12
+ output += '</td></tr></table></td></tr>'
13
+ end
14
+ output += '</table></body></html>'
15
+ output
16
+ end
17
+
18
+ private
19
+
20
+ def render_policy(result)
21
+ output = ''
22
+ if result[:file_results]['AWS::IAM::Policy'] != {}
23
+ output += '<ul>'
24
+ result[:file_results]['AWS::IAM::Policy'].each do |k, v|
25
+ output += "<li>#{k}=#{v}</li>"
26
+ end
27
+ output += '</ul>'
28
+ end
29
+ output
30
+ end
31
+
32
+ def render_role(result)
33
+ output = ''
34
+ if result[:file_results]['AWS::IAM::Role'] != {}
35
+ output += '<ul>'
36
+ result[:file_results]['AWS::IAM::Role'].each do |role_id, policy_map|
37
+ policy_map.each do |policy_name, metric|
38
+ output += "<li>#{role_id}/#{policy_name}=#{metric}</li>"
39
+ end
40
+ end
41
+ output += '</ul>'
42
+ end
43
+ output
44
+ end
45
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'statement_metric'
4
+
5
+ class PolicyDocumentMetric
6
+ def metric(policy_document)
7
+ policy_document.statements.reduce(0) do |aggregate, statement|
8
+ aggregate + StatementMetric.new.metric(statement)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cfn-nag/template_discovery'
4
+ require 'cfn-model'
5
+ require_relative 'policy_document_metric'
6
+
7
+ class SPCM
8
+ DEFAULT_TEMPLATE_PATTERN = '..*\.json$|..*\.yaml$|..*\.yml$|..*\.template$'
9
+
10
+ def aggregate_metrics(input_path:,
11
+ parameter_values_path: nil,
12
+ condition_values_path: nil,
13
+ template_pattern: DEFAULT_TEMPLATE_PATTERN)
14
+ parameter_values_string = parameter_values_path.nil? ? nil : IO.read(parameter_values_path)
15
+ condition_values_string = condition_values_path.nil? ? nil : IO.read(condition_values_path)
16
+
17
+ templates = TemplateDiscovery.new.discover_templates(input_json_path: input_path,
18
+ template_pattern: template_pattern)
19
+ aggregate_results = []
20
+ templates.each do |template|
21
+ aggregate_results << {
22
+ filename: template,
23
+ file_results: metric(
24
+ cloudformation_string: IO.read(template),
25
+ parameter_values_string: parameter_values_string,
26
+ condition_values_string: condition_values_string
27
+ )
28
+ }
29
+ end
30
+ aggregate_results
31
+ end
32
+
33
+ def metric(cloudformation_string:, parameter_values_string: nil, condition_values_string: nil)
34
+ cfn_model = CfnParser.new.parse cloudformation_string,
35
+ parameter_values_string,
36
+ false,
37
+ condition_values_string
38
+
39
+ metric_impl(cfn_model)
40
+ end
41
+
42
+ def metric_impl(cfn_model)
43
+ policy_documents = {
44
+ 'AWS::IAM::Policy' => {},
45
+ 'AWS::IAM::Role' => {}
46
+ }
47
+
48
+ cfn_model.resources_by_type('AWS::IAM::Policy').each do |policy|
49
+ update_policy_metric(policy_documents, policy)
50
+ end
51
+
52
+ cfn_model.resources_by_type('AWS::IAM::Role').each do |role|
53
+ role.policy_objects.each do |policy|
54
+ update_role_policy_metric(policy_documents, role, policy)
55
+ end
56
+ end
57
+
58
+ policy_documents
59
+ end
60
+
61
+ private
62
+
63
+ def update_policy_metric(policy_documents, policy)
64
+ metric = PolicyDocumentMetric.new.metric(policy.policy_document)
65
+ policy_documents['AWS::IAM::Policy'][policy.logical_resource_id] = metric
66
+ end
67
+
68
+ def update_role_policy_metric(policy_documents, role, policy)
69
+ metric = PolicyDocumentMetric.new.metric(policy.policy_document)
70
+
71
+ if policy_documents['AWS::IAM::Role'][role.logical_resource_id]
72
+ policy_documents['AWS::IAM::Role'][role.logical_resource_id][policy.policy_name.to_s] = metric
73
+ else
74
+ policy_documents['AWS::IAM::Role'][role.logical_resource_id] = {
75
+ policy.policy_name.to_s => metric
76
+ }
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'weights'
4
+ require_relative 'condition_metric'
5
+ require 'set'
6
+
7
+ class StatementMetric
8
+ include Weights
9
+
10
+ # rubocop:disable Metrics/AbcSize
11
+ def metric(statement)
12
+ aggregate = weights[:Base_Statement]
13
+
14
+ aggregate += effect_metrics(statement)
15
+ aggregate += inversion_metrics(statement)
16
+ aggregate += extra_service_count(statement) * weights[:Extra_Service]
17
+ aggregate += misaligned_resource_action_count(statement) * weights[:Resource_Action_NotAligned]
18
+ aggregate += mixed_wildcard(statement) * weights[:Mixed_Wildcard]
19
+
20
+ aggregate += ConditionMetric.new.metric(statement) unless statement.condition.nil?
21
+
22
+ aggregate
23
+ end
24
+ # rubocop:enable Metrics/AbcSize
25
+
26
+ private
27
+
28
+ def effect_metrics(statement)
29
+ aggregate = 0
30
+ aggregate += weights[:Deny] if statement.effect == 'Deny'
31
+ aggregate += weights[:Allow] if statement.effect == 'Allow'
32
+ aggregate
33
+ end
34
+
35
+ def inversion_metrics(statement)
36
+ aggregate = 0
37
+ aggregate += weights[:NotAction] unless statement.not_actions.empty?
38
+ aggregate += weights[:NotResource] unless statement.not_resources.empty?
39
+ aggregate
40
+ end
41
+
42
+ def mixed_wildcard(statement)
43
+ count = 0
44
+ count += 1 if action_service_names(statement).include?('*') && action_service_names(statement).size > 1
45
+ count += 1 if resource_service_names(statement).include?('*') && resource_service_names(statement).size > 1
46
+ count
47
+ end
48
+
49
+ def misaligned_resource_action_count(statement)
50
+ return 0 if resource(statement) == ['*'] || action(statement) == ['*']
51
+
52
+ resource_service_names = resource(statement).map { |resource_arn| resource_service_name(resource_arn) }
53
+ action_service_names = action(statement).map { |action| action_service_name(action) }
54
+
55
+ (set_without_wildcard(resource_service_names) - set_without_wildcard(action_service_names)).size
56
+ end
57
+
58
+ # rubocop:disable Naming/AccessorMethodName
59
+ def set_without_wildcard(array)
60
+ Set.new(array).delete('*')
61
+ end
62
+ # rubocop:enable Naming/AccessorMethodName
63
+
64
+ def extra_service_count(statement)
65
+ service_names = Set.new(action_service_names(statement) + resource_service_names(statement)).delete('*')
66
+ [service_names.size - 1, 0].max
67
+ end
68
+
69
+ def action_service_names(statement)
70
+ action(statement).map { |action| action_service_name(action) }
71
+ end
72
+
73
+ def resource_service_names(statement)
74
+ resource(statement).map { |resource_arn| resource_service_name(resource_arn) }
75
+ end
76
+
77
+ def action_service_name(action)
78
+ return '*' if action == '*'
79
+
80
+ return action unless action.is_a?(String)
81
+
82
+ action.split(':')[0]
83
+ end
84
+
85
+ def resource_service_name(resource_arn)
86
+ return '*' if resource_arn == '*'
87
+
88
+ return resource_arn unless resource_arn.is_a?(String)
89
+
90
+ resource_arn.split(':')[2]
91
+ end
92
+
93
+ def action(statement)
94
+ return statement.actions unless statement.actions.empty?
95
+
96
+ statement.not_actions
97
+ end
98
+
99
+ def resource(statement)
100
+ return statement.resources unless statement.resources.empty?
101
+
102
+ statement.not_resources
103
+ end
104
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Weights
4
+ def weights
5
+ {
6
+ Base_Statement: 1,
7
+ Allow: 0,
8
+ Deny: 1,
9
+ NotAction: 1,
10
+ NotResource: 1,
11
+ Mixed_Wildcard: 1,
12
+
13
+ Extra_Service: 2,
14
+ Resource_Action_NotAligned: 1,
15
+
16
+ Condition: 2,
17
+ IfExists: 1,
18
+ Null: 1,
19
+ PolicyVariables: 1
20
+ }
21
+ end
22
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cfn-model'
4
+
5
+ ##
6
+ # Mix-in with metadata handling routines for the CustomRuleLoader
7
+ module Metadata
8
+ # XXX given mangled_metadatas is never used or returned,
9
+ # STDERR emit can be moved to unless block
10
+ def validate_cfn_nag_metadata(cfn_model)
11
+ mangled_metadatas = collect_mangled_metadata(cfn_model)
12
+ mangled_metadatas.each do |mangled_metadata|
13
+ logical_resource_id = mangled_metadata.first
14
+ mangled_rules = mangled_metadata[1]
15
+
16
+ STDERR.puts "#{logical_resource_id} has missing cfn_nag suppression rule id: #{mangled_rules}"
17
+ end
18
+ end
19
+
20
+ def cfn_model_with_suppressed_resources_removed(cfn_model:,
21
+ rule_id:,
22
+ allow_suppression:,
23
+ print_suppression:)
24
+ return cfn_model unless allow_suppression
25
+
26
+ cfn_model = cfn_model.copy
27
+
28
+ cfn_model.resources.delete_if do |logical_resource_id, resource|
29
+ rules_to_suppress = rules_to_suppress resource
30
+ if rules_to_suppress.nil?
31
+ false
32
+ else
33
+ suppress_resource?(rules_to_suppress, rule_id, logical_resource_id, print_suppression)
34
+ end
35
+ end
36
+ cfn_model
37
+ end
38
+
39
+ private
40
+
41
+ def suppress_resource?(rules_to_suppress, rule_id, logical_resource_id, print_suppression)
42
+ found_suppression_rule = rules_to_suppress.find do |rule_to_suppress|
43
+ next if rule_to_suppress['id'].nil?
44
+
45
+ rule_to_suppress['id'] == rule_id
46
+ end
47
+ if found_suppression_rule && print_suppression
48
+ message = "Suppressing #{rule_id} on #{logical_resource_id} for reason: #{found_suppression_rule['reason']}"
49
+ STDERR.puts message
50
+ end
51
+ !found_suppression_rule.nil?
52
+ end
53
+
54
+ def rules_to_suppress(resource)
55
+ if resource.metadata &&
56
+ resource.metadata['cfn_nag'] &&
57
+ resource.metadata['cfn_nag']['rules_to_suppress']
58
+
59
+ resource.metadata['cfn_nag']['rules_to_suppress']
60
+ end
61
+ end
62
+
63
+ def collect_mangled_metadata(cfn_model)
64
+ mangled_metadatas = []
65
+ cfn_model.resources.each do |logical_resource_id, resource|
66
+ resource_rules_to_suppress = rules_to_suppress resource
67
+ next if resource_rules_to_suppress.nil?
68
+
69
+ mangled_rules = resource_rules_to_suppress.select do |rule_to_suppress|
70
+ rule_to_suppress['id'].nil?
71
+ end
72
+ unless mangled_rules.empty?
73
+ mangled_metadatas << [logical_resource_id, mangled_rules]
74
+ end
75
+ end
76
+ mangled_metadatas
77
+ end
78
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfn-nag
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.61
4
+ version: 0.6.4
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-06 00:00:00.000000000 Z
11
+ date: 2020-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -72,14 +72,14 @@ dependencies:
72
72
  requirements:
73
73
  - - '='
74
74
  - !ruby/object:Gem::Version
75
- version: 0.5.0
75
+ version: 0.5.1
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - '='
81
81
  - !ruby/object:Gem::Version
82
- version: 0.5.0
82
+ version: 0.5.1
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: logging
85
85
  requirement: !ruby/object:Gem::Requirement
@@ -156,12 +156,14 @@ executables:
156
156
  - cfn_nag
157
157
  - cfn_nag_rules
158
158
  - cfn_nag_scan
159
+ - spcm_scan
159
160
  extensions: []
160
161
  extra_rdoc_files: []
161
162
  files:
162
163
  - bin/cfn_nag
163
164
  - bin/cfn_nag_rules
164
165
  - bin/cfn_nag_scan
166
+ - bin/spcm_scan
165
167
  - lib/cfn-nag.rb
166
168
  - lib/cfn-nag/base_rule.rb
167
169
  - lib/cfn-nag/blacklist_loader.rb
@@ -295,6 +297,7 @@ files:
295
297
  - lib/cfn-nag/custom_rules/S3BucketPolicyWildcardPrincipalRule.rb
296
298
  - lib/cfn-nag/custom_rules/S3BucketPublicReadAclRule.rb
297
299
  - lib/cfn-nag/custom_rules/S3BucketPublicReadWriteAclRule.rb
300
+ - lib/cfn-nag/custom_rules/SPCMRule.rb
298
301
  - lib/cfn-nag/custom_rules/SageMakerEndpointConfigKmsKeyIdRule.rb
299
302
  - lib/cfn-nag/custom_rules/SageMakerNotebookInstanceKmsKeyIdRule.rb
300
303
  - lib/cfn-nag/custom_rules/SecretsManagerSecretKmsKeyIdRule.rb
@@ -327,7 +330,14 @@ files:
327
330
  - lib/cfn-nag/custom_rules/password_base_rule.rb
328
331
  - lib/cfn-nag/custom_rules/resource_base_rule.rb
329
332
  - lib/cfn-nag/custom_rules/sub_property_with_list_password_base_rule.rb
333
+ - lib/cfn-nag/iam_complexity_metric/condition_metric.rb
334
+ - lib/cfn-nag/iam_complexity_metric/html_results_renderer.rb
335
+ - lib/cfn-nag/iam_complexity_metric/policy_document_metric.rb
336
+ - lib/cfn-nag/iam_complexity_metric/spcm.rb
337
+ - lib/cfn-nag/iam_complexity_metric/statement_metric.rb
338
+ - lib/cfn-nag/iam_complexity_metric/weights.rb
330
339
  - lib/cfn-nag/ip_addr.rb
340
+ - lib/cfn-nag/metadata.rb
331
341
  - lib/cfn-nag/profile_loader.rb
332
342
  - lib/cfn-nag/result_view/colored_stdout_results.rb
333
343
  - lib/cfn-nag/result_view/json_results.rb
@@ -371,7 +381,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
371
381
  - !ruby/object:Gem::Version
372
382
  version: '0'
373
383
  requirements: []
374
- rubygems_version: 3.1.2
384
+ rubygems_version: 3.1.3
375
385
  signing_key:
376
386
  specification_version: 4
377
387
  summary: cfn-nag