cfn-nag 0.0.34 → 0.0.35

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: 54f56e1bc63397251c05089f1350fed67080b941
4
- data.tar.gz: 625d5593cd5a4ac8c048d0ab413979805d526370
3
+ metadata.gz: 599ac76a7c5bb73f2e8ad348f408f1945c9556c8
4
+ data.tar.gz: 13521f759054aba5f2abcd434e71b46c36b95dc7
5
5
  SHA512:
6
- metadata.gz: f45991e43fb6ac8f6991e96beac5c5c758a8ccdfc2cff3bed0f26153e05e5cd15a808b659dc90ba6d93d17839f2f94b9e496695af315b6b104584411e4139612
7
- data.tar.gz: 300743ae7f6a3eb7e02047676ee3002a64a46fea761775a5fcffe01d81792e0ee7bc31a8ba6ab01255670bd9933c894133ae197358f66fdd0efc3a56f1094c7d
6
+ metadata.gz: 02d05593b7c89b552b8b5ffe3c0cbb353b9a3d80f15c27a3920279b88a57f0639026b5eb220adaaff5b4ad8cbdd6172f0d2a38931789147e0f7fc73e2167bd26
7
+ data.tar.gz: 4ad062cf1e1040dd21549569d3ebc621dd8c63005851f6c9bdcb5858eb5294cdfdeec1ad0e235f93c3e111412c35e786ebc31e0c5ed3aa49f6fdfc801a242b8a
data/bin/cfn_nag CHANGED
@@ -7,13 +7,22 @@ opts = Trollop::options do
7
7
  opt :input_json_path, 'CloudFormation template to nag on or directory of templates - all *.json and *.template recursively', type: :io, required: true
8
8
  opt :output_format, 'Format of results: [txt, json]', type: :string, default: 'txt'
9
9
  opt :debug, 'Enable debug output', type: :boolean, required: false, default: false
10
- opt :rule_directory, 'Extra rule directories', type: :strings, require: false, default: [], multi: true
10
+ opt :rule_directory, 'Extra rule directories', type: :strings, required: false, default: [], multi: true
11
+ opt :profile_path, 'Path to a profile file', type: :io, required: false, default: nil
11
12
  end
12
13
 
13
14
  Trollop::die(:output_format,
14
15
  'Must be txt or json') unless %w(txt json).include?(opts[:output_format])
15
16
 
16
17
  CfnNag::configure_logging(opts)
17
- exit CfnNag.new.audit(input_json_path: opts[:input_json_path],
18
- output_format: opts[:output_format],
19
- rule_directories: opts[:rule_directory])
18
+
19
+ profile_definition = nil
20
+ unless opts[:profile_path].nil?
21
+ profile_definition = IO.read(opts[:profile_path])
22
+ end
23
+
24
+ cfn_nag = CfnNag.new(profile_definition: profile_definition)
25
+
26
+ exit cfn_nag.audit(input_json_path: opts[:input_json_path],
27
+ output_format: opts[:output_format],
28
+ rule_directories: opts[:rule_directory])
data/bin/cfn_nag_rules CHANGED
@@ -1,4 +1,17 @@
1
1
  #!/usr/bin/env ruby
2
+ require 'trollop'
2
3
  require 'cfn_nag'
3
4
 
4
- CfnNag.new.dump_rules
5
+ opts = Trollop::options do
6
+ opt :rule_directory, 'Extra rule directories', type: :strings, required: false, default: [], multi: true
7
+ opt :profile_path, 'Path to a profile file', type: :io, required: false, default: nil
8
+ end
9
+
10
+ profile_definition = nil
11
+ unless opts[:profile_path].nil?
12
+ profile_definition = IO.read(opts[:profile_path])
13
+ end
14
+
15
+ cfn_nag = CfnNag.new(profile_definition: profile_definition)
16
+
17
+ cfn_nag.dump_rules(rule_directories: opts[:rule_directory])
data/lib/cfn_nag.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require_relative 'rule'
2
2
  require_relative 'custom_rule_loader'
3
+ require_relative 'rule_registry'
4
+ require_relative 'profile_loader'
3
5
  require_relative 'model/cfn_model'
4
6
  require_relative 'result_view/simple_stdout_results'
5
7
  require_relative 'result_view/json_results'
@@ -8,12 +10,17 @@ require 'tempfile'
8
10
  class CfnNag
9
11
  include Rule
10
12
 
11
- def initialize
13
+ def initialize(profile_definition: nil)
12
14
  @warning_registry = []
13
15
  @violation_registry = []
16
+ @rule_registry = RuleRegistry.new
17
+ @custom_rule_loader = CustomRuleLoader.new(@rule_registry)
18
+ @profile_definition = profile_definition
14
19
  end
15
20
 
16
- def dump_rules
21
+ def dump_rules(rule_directories: [])
22
+ validate_extra_rule_directories(rule_directories)
23
+
17
24
  dummy_cfn = <<-END
18
25
  {
19
26
  "Resources": {
@@ -30,18 +37,32 @@ class CfnNag
30
37
  Tempfile.open('tempfile') do |dummy_cfn_template|
31
38
  dummy_cfn_template.write dummy_cfn
32
39
  dummy_cfn_template.rewind
33
- audit_file(input_json_path: dummy_cfn_template.path, rule_directories: [])
40
+ audit_file(input_json_path: dummy_cfn_template.path,
41
+ rule_directories: rule_directories)
34
42
  end
35
43
 
36
- CustomRuleLoader.new.custom_rule_registry.each do |rule_class|
37
- @violation_registry << rule_class.new.rule_text
44
+ profile = nil
45
+ unless @profile_definition.nil?
46
+ profile = ProfileLoader.new(@rule_registry).load(profile_definition: @profile_definition)
38
47
  end
39
48
 
40
49
  puts 'WARNING VIOLATIONS:'
41
- puts @warning_registry.sort
42
- puts
50
+ @rule_registry.warnings.sort {|left, right| left.id <=> right.id}.each do |warning|
51
+ if profile.nil?
52
+ puts "#{warning.id} #{warning.message}"
53
+ else
54
+ puts "#{warning.id} #{warning.message}" if profile.execute_rule?(warning.id)
55
+ end
56
+
57
+ end
43
58
  puts 'FAILING VIOLATIONS:'
44
- puts @violation_registry.sort
59
+ @rule_registry.failings.sort {|left, right| left.id <=> right.id}.each do |failing|
60
+ if profile.nil?
61
+ puts "#{failing.id} #{failing.message}"
62
+ else
63
+ puts "#{failing.id} #{failing.message}" if profile.execute_rule?(failing.id)
64
+ end
65
+ end
45
66
  end
46
67
 
47
68
  def audit(input_json_path:,
@@ -88,12 +109,14 @@ class CfnNag
88
109
  logger.add_appenders Logging.appenders.stdout
89
110
  end
90
111
 
91
- def audit_template(input_json:, rule_directories: [])
112
+ def audit_template(input_json:,
113
+ rule_directories: [])
92
114
  @stop_processing = false
93
115
  @violations = []
94
116
 
95
117
  unless legal_json?(input_json)
96
- @violations << Violation.new(type: Violation::FAILING_VIOLATION,
118
+ @violations << Violation.new(id: 'FATAL',
119
+ type: Violation::FAILING_VIOLATION,
97
120
  message: 'not even legit JSON',
98
121
  violating_code: input_json)
99
122
  @stop_processing = true
@@ -103,6 +126,8 @@ class CfnNag
103
126
 
104
127
  @violations += custom_rules input_json unless @stop_processing == true
105
128
 
129
+ @violations = filter_violations_by_profile @violations
130
+
106
131
  {
107
132
  failure_count: Rule::count_failures(@violations),
108
133
  violations: @violations
@@ -111,6 +136,17 @@ class CfnNag
111
136
 
112
137
  private
113
138
 
139
+ def filter_violations_by_profile(violations)
140
+ profile = nil
141
+ unless @profile_definition.nil?
142
+ profile = ProfileLoader.new(@rule_registry).load(profile_definition: @profile_definition)
143
+ end
144
+
145
+ violations.reject do |violation|
146
+ not profile.nil? and not profile.execute_rule?(violation.id)
147
+ end
148
+ end
149
+
114
150
  def validate_extra_rule_directories(rule_directories)
115
151
  rule_directories.flatten.each do |rule_directory|
116
152
  fail "Not a real directory #{rule_directory}" unless File.directory? rule_directory
@@ -118,11 +154,13 @@ class CfnNag
118
154
  end
119
155
 
120
156
 
121
- def render_results(aggregate_results:,output_format:)
157
+ def render_results(aggregate_results:,
158
+ output_format:)
122
159
  results_renderer(output_format).new.render(aggregate_results)
123
160
  end
124
161
 
125
- def audit_file(input_json_path:, rule_directories:)
162
+ def audit_file(input_json_path:,
163
+ rule_directories:)
126
164
  audit_template(input_json: IO.read(input_json_path),
127
165
  rule_directories: rule_directories)
128
166
  end
@@ -169,21 +207,11 @@ class CfnNag
169
207
  not system("#{command} > /dev/null 2>&1").nil?
170
208
  end
171
209
 
172
- def jruby_in_a_jar?
173
- __dir__.start_with? '/uri:classloader'
174
- end
175
-
176
210
  def generic_json_rules(input_json, rule_directories)
177
211
  unless command? 'jq'
178
212
  fail 'jq executable must be available in PATH'
179
213
  end
180
-
181
- if jruby_in_a_jar?
182
- rules = %w(basic_rules cfn_rules cidr_rules cloudfront_rules ebs_rules iam_policy_rules iam_user_rules lambda_rules loadbalancer_rules port_rules s3_bucket_rules sns_rules sqs_rules)
183
- rules = rules.map { |rule| File.join(__dir__, 'json_rules', "#{rule}.rb")[1..-1] }
184
- else
185
- rules = Dir[File.join(__dir__, 'json_rules', '*.rb')].sort
186
- end
214
+ rules = Dir[File.join(__dir__, 'json_rules', '*.rb')].sort
187
215
 
188
216
  rules.each do |rule_file|
189
217
  @input_json = input_json
@@ -201,6 +229,6 @@ class CfnNag
201
229
  end
202
230
 
203
231
  def custom_rules(input_json)
204
- CustomRuleLoader.new.custom_rules(input_json)
232
+ @custom_rule_loader.custom_rules(input_json)
205
233
  end
206
234
  end
@@ -18,19 +18,26 @@ class CustomRuleLoader
18
18
  @custom_rule_directory
19
19
  end
20
20
 
21
- def initialize
21
+ def initialize(rule_registry)
22
22
  @custom_rule_registry = [
23
23
  SecurityGroupMissingEgressRule,
24
24
  UserMissingGroupRule,
25
25
  UnencryptedS3PutObjectAllowedRule
26
26
  ]
27
27
  @violations = []
28
+ @rule_registry = rule_registry
29
+ discover_rules
28
30
  end
29
31
 
30
32
  def custom_rules(input_json)
31
- discover_rules
33
+ @violations = []
34
+
32
35
  @custom_rule_registry.each do |rule_class|
33
36
  rule = rule_class.new
37
+ @rule_registry.definition(id: rule.rule_id,
38
+ type: rule.rule_type,
39
+ message: rule.rule_text)
40
+
34
41
  if rule.respond_to? 'custom_parsers'
35
42
  rule.custom_parsers.each do |custom_parser|
36
43
  ParserRegistry.instance.add_parser custom_parser[0], custom_parser[1]
@@ -6,6 +6,14 @@ class SecurityGroupMissingEgressRule
6
6
  'Missing egress rule means all traffic is allowed outbound. Make this explicit if it is desired configuration'
7
7
  end
8
8
 
9
+ def rule_type
10
+ Violation::FAILING_VIOLATION
11
+ end
12
+
13
+ def rule_id
14
+ 'F1000'
15
+ end
16
+
9
17
  def audit(cfn_model)
10
18
  logical_resource_ids = []
11
19
  cfn_model.security_groups.each do |security_group|
@@ -15,7 +23,8 @@ class SecurityGroupMissingEgressRule
15
23
  end
16
24
 
17
25
  if logical_resource_ids.size > 0
18
- Violation.new(type: Violation::FAILING_VIOLATION,
26
+ Violation.new(id: rule_id,
27
+ type: rule_type,
19
28
  message: rule_text,
20
29
  logical_resource_ids: logical_resource_ids)
21
30
  else
@@ -7,6 +7,14 @@ class UnencryptedS3PutObjectAllowedRule
7
7
  'It appears that the S3 Bucket Policy allows s3:PutObject without server-side encryption'
8
8
  end
9
9
 
10
+ def rule_type
11
+ Violation::WARNING
12
+ end
13
+
14
+ def rule_id
15
+ 'W1000'
16
+ end
17
+
10
18
  def audit(cfn_model)
11
19
  logical_resource_ids = []
12
20
  cfn_model.bucket_policies.each do |bucket_policy|
@@ -19,7 +27,8 @@ class UnencryptedS3PutObjectAllowedRule
19
27
  end
20
28
 
21
29
  if logical_resource_ids.size > 0
22
- Violation.new(type: Violation::WARNING,
30
+ Violation.new(id: rule_id,
31
+ type: rule_type,
23
32
  message: rule_text,
24
33
  logical_resource_ids: logical_resource_ids)
25
34
  else
@@ -6,6 +6,14 @@ class UserMissingGroupRule
6
6
  'User is not assigned to a group'
7
7
  end
8
8
 
9
+ def rule_type
10
+ Violation::FAILING_VIOLATION
11
+ end
12
+
13
+ def rule_id
14
+ 'F2000'
15
+ end
16
+
9
17
  def audit(cfn_model)
10
18
  logical_resource_ids = []
11
19
  cfn_model.iam_users.each do |iam_user|
@@ -15,7 +23,8 @@ class UserMissingGroupRule
15
23
  end
16
24
 
17
25
  if logical_resource_ids.size > 0
18
- Violation.new(type: Violation::FAILING_VIOLATION,
26
+ Violation.new(id: rule_id,
27
+ type: rule_type,
19
28
  message: rule_text,
20
29
  logical_resource_ids: logical_resource_ids)
21
30
  else
@@ -1,4 +1,5 @@
1
- raw_fatal_assertion jq: '.Resources|length > 0',
1
+ raw_fatal_assertion id: 'FATAL',
2
+ jq: '.Resources|length > 0',
2
3
  message: 'A CloudFormation template must have at least 1 resource'
3
4
 
4
5
 
@@ -14,7 +15,8 @@ raw_fatal_assertion jq: '.Resources|length > 0',
14
15
  AWS::EC2::SecurityGroupIngress
15
16
  AWS::EC2::SecurityGroupEgress
16
17
  ).each do |resource_must_have_properties|
17
- fatal_violation jq: "[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == \"#{resource_must_have_properties}\" and .Properties == null)]|map(.LogicalResourceId)",
18
+ fatal_violation id: 'FATAL',
19
+ jq: "[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == \"#{resource_must_have_properties}\" and .Properties == null)]|map(.LogicalResourceId)",
18
20
  message: "#{resource_must_have_properties} must have Properties"
19
21
  end
20
22
 
@@ -32,7 +34,8 @@ missing_reference_jq = <<END
32
34
  )|if length==0 then false else . end
33
35
  END
34
36
 
35
- raw_fatal_violation jq: missing_reference_jq,
37
+ raw_fatal_violation id: 'FATAL',
38
+ jq: missing_reference_jq,
36
39
  message: 'All Ref and Fn::GetAtt must reference existing logical resource ids'
37
40
 
38
41
 
@@ -40,6 +43,7 @@ raw_fatal_violation jq: missing_reference_jq,
40
43
  AWS::EC2::SecurityGroupIngress
41
44
  AWS::EC2::SecurityGroupEgress
42
45
  ).each do |xgress|
43
- fatal_violation jq: "[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == \"#{xgress}\" and .Properties.GroupName != null)]|map(.LogicalResourceId)",
46
+ fatal_violation id: 'FATAL',
47
+ jq: "[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == \"#{xgress}\" and .Properties.GroupName != null)]|map(.LogicalResourceId)",
44
48
  message: "#{xgress} must not have GroupName - EC2 classic is a no-go!"
45
49
  end
@@ -1,3 +1,4 @@
1
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Metadata."AWS::CloudFormation::Authentication") ]|'\
1
+ warning id: 'W1',
2
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Metadata."AWS::CloudFormation::Authentication") ]|'\
2
3
  'map(.LogicalResourceId) ',
3
4
  message: 'Specifying credentials in the template itself is probably not the safest thing'
@@ -1,31 +1,38 @@
1
1
  ##### inline ingress
2
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupIngress|type == "object"))|select(.Properties.SecurityGroupIngress.CidrIp? == "0.0.0.0/0")]|map(.LogicalResourceId)',
2
+ warning id: 'W2',
3
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupIngress|type == "object"))|select(.Properties.SecurityGroupIngress.CidrIp? == "0.0.0.0/0")]|map(.LogicalResourceId)',
3
4
  message: 'Security Groups found with cidr open to world on ingress. This should never be true on instance. Permissible on ELB'
4
5
 
5
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupIngress|type == "array"))|first(select(.Properties.SecurityGroupIngress[].CidrIp? == "0.0.0.0/0"))]|map(.LogicalResourceId)',
6
+ warning id: 'W3',
7
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupIngress|type == "array"))|first(select(.Properties.SecurityGroupIngress[].CidrIp? == "0.0.0.0/0"))]|map(.LogicalResourceId)',
6
8
  message: 'Security Groups found with cidr open to world on ingress array. This should never be true on instance. Permissible on ELB'
7
9
 
8
10
  ##### external ingress
9
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroupIngress")|select(.Properties.CidrIp? == "0.0.0.0/0")]|map(.LogicalResourceId)',
11
+ warning id: 'W4',
12
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroupIngress")|select(.Properties.CidrIp? == "0.0.0.0/0")]|map(.LogicalResourceId)',
10
13
  message: 'Security Group Standalone Ingress found with cidr open to world. This should never be true on instance. Permissible on ELB'
11
14
 
12
15
 
13
16
  ###### inline egress
14
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupEgress|type == "object"))|select(.Properties.SecurityGroupEgress.CidrIp? == "0.0.0.0/0")]|map(.LogicalResourceId)',
17
+ warning id: 'W5',
18
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupEgress|type == "object"))|select(.Properties.SecurityGroupEgress.CidrIp? == "0.0.0.0/0")]|map(.LogicalResourceId)',
15
19
  message: 'Security Groups found with cidr open to world on egress'
16
20
 
17
21
 
18
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupEgress|type == "array"))|first(select(.Properties.SecurityGroupEgress[].CidrIp? == "0.0.0.0/0"))]|map(.LogicalResourceId)',
22
+ warning id: 'W6',
23
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupEgress|type == "array"))|first(select(.Properties.SecurityGroupEgress[].CidrIp? == "0.0.0.0/0"))]|map(.LogicalResourceId)',
19
24
  message: 'Security Groups found with cidr open to world on egress array'
20
25
 
21
26
 
22
27
  ##### external egress
23
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroupEgress")|select(.Properties.CidrIp? == "0.0.0.0/0")]|map(.LogicalResourceId)',
28
+ warning id: 'W7',
29
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroupEgress")|select(.Properties.CidrIp? == "0.0.0.0/0")]|map(.LogicalResourceId)',
24
30
  message: 'Security Group Standalone Egress found with cidr open to world.'
25
31
 
26
32
 
27
33
  # BEWARE with escapes \d -> \\\d because of how the escapes get munged from ruby through to shell
28
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroupIngress" and .Properties.CidrIp|type == "string")|select(.Properties.CidrIp | test("^\\\d{1,3}\\\.\\\d{1,3}\\\.\\\d{1,3}\\\.\\\d{1,3}/(?!32)$") )]|map(.LogicalResourceId)',
34
+ warning id: 'W8',
35
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroupIngress" and .Properties.CidrIp|type == "string")|select(.Properties.CidrIp | test("^\\\d{1,3}\\\.\\\d{1,3}\\\.\\\d{1,3}\\\.\\\d{1,3}/(?!32)$") )]|map(.LogicalResourceId)',
29
36
  message: 'Security Group Standalone Ingress cidr found that is not /32'
30
37
 
31
38
  non_32_cidr_jq_expression = <<END
@@ -57,5 +64,6 @@ non_32_cidr_jq_expression = <<END
57
64
  ]|map(.LogicalResourceId)
58
65
  END
59
66
 
60
- warning jq: non_32_cidr_jq_expression,
67
+ warning id: 'W9',
68
+ jq: non_32_cidr_jq_expression,
61
69
  message: 'Security Groups found with cidr that is not /32'
@@ -1,3 +1,4 @@
1
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::CloudFront::Distribution")|'\
1
+ warning id: 'W10',
2
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::CloudFront::Distribution")|'\
2
3
  'select(.Properties.DistributionConfig.Logging == null)]|map(.LogicalResourceId) ',
3
4
  message: 'CloudFront Distribution should enable access logging'
@@ -1,3 +1,4 @@
1
- violation jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::Volume")|'\
1
+ violation id: 'F1',
2
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::Volume")|'\
2
3
  'select(.Properties.Encrypted == null or .Properties.Encrypted == false)]|map(.LogicalResourceId) ',
3
4
  message: 'EBS volume should have server-side encryption enabled'
@@ -6,21 +6,25 @@ def wildcard_action:
6
6
  end;
7
7
  END
8
8
 
9
- violation jq: wildcard_action_filter +
9
+ violation id: 'F2',
10
+ jq: wildcard_action_filter +
10
11
  "[#{resources_by_type('AWS::IAM::Role')}|select(.Properties.AssumeRolePolicyDocument|wildcard_action)]|map(.LogicalResourceId) ",
11
12
  message: 'IAM role should not allow * action on its trust policy'
12
13
 
13
- violation jq: wildcard_action_filter +
14
+ violation id: 'F3',
15
+ jq: wildcard_action_filter +
14
16
  "[#{resources_by_type('AWS::IAM::Role')}|select(.Properties.Policies !=null)|select(.Properties.Policies[].PolicyDocument|wildcard_action)]|map(.LogicalResourceId)",
15
17
  message: 'IAM role should not allow * action on its permissions policy'
16
18
 
17
19
 
18
- violation jq: wildcard_action_filter +
20
+ violation id: 'F4',
21
+ jq: wildcard_action_filter +
19
22
  "[#{resources_by_type('AWS::IAM::Policy')}|select(.Properties.PolicyDocument|wildcard_action)]|map(.LogicalResourceId) ",
20
23
  message: 'IAM policy should not allow * action'
21
24
 
22
25
 
23
- violation jq: wildcard_action_filter +
26
+ violation id: 'F5',
27
+ jq: wildcard_action_filter +
24
28
  "[#{resources_by_type('AWS::IAM::ManagedPolicy')}|select(.Properties.PolicyDocument|wildcard_action)]|map(.LogicalResourceId) ",
25
29
  message: 'IAM managed policy should not allow * action'
26
30
 
@@ -32,16 +36,19 @@ def wildcard_resource:
32
36
  end;
33
37
  END
34
38
 
35
- warning jq: wildcard_resource_filter +
39
+ warning id: 'W11',
40
+ jq: wildcard_resource_filter +
36
41
  "[#{resources_by_type('AWS::IAM::Role')}|select(.Properties.Policies !=null)|select(.Properties.Policies[].PolicyDocument|wildcard_resource)]|map(.LogicalResourceId)",
37
42
  message: 'IAM role should not allow * resource on its permissions policy'
38
43
 
39
44
 
40
- warning jq: wildcard_resource_filter +
45
+ warning id: 'W12',
46
+ jq: wildcard_resource_filter +
41
47
  "[#{resources_by_type('AWS::IAM::Policy')}|select(.Properties.PolicyDocument|wildcard_resource)]|map(.LogicalResourceId)",
42
48
  message: 'IAM policy should not allow * resource'
43
49
 
44
- warning jq: wildcard_resource_filter +
50
+ warning id: 'W13',
51
+ jq: wildcard_resource_filter +
45
52
  "[#{resources_by_type('AWS::IAM::ManagedPolicy')}|select(.Properties.PolicyDocument|wildcard_resource)]|map(.LogicalResourceId)",
46
53
  message: 'IAM managed policy should not allow * resource'
47
54
 
@@ -53,34 +60,41 @@ def allow_not_action:
53
60
  end;
54
61
  END
55
62
 
56
- warning jq: allow_not_action_filter +
63
+ warning id: 'W14',
64
+ jq: allow_not_action_filter +
57
65
  "[#{resources_by_type('AWS::IAM::Role')}|select(.Properties.AssumeRolePolicyDocument|allow_not_action)]|map(.LogicalResourceId)",
58
66
  message: 'IAM role should not allow Allow+NotAction on trust permissinos'
59
67
 
60
68
 
61
- warning jq: allow_not_action_filter +
69
+ warning id: 'W15',
70
+ jq: allow_not_action_filter +
62
71
  "[#{resources_by_type('AWS::IAM::Role')}|select(.Properties.Policies !=null)|select(.Properties.Policies[].PolicyDocument|allow_not_action)]|map(.LogicalResourceId)",
63
72
  message: 'IAM role should not allow Allow+NotAction'
64
73
 
65
74
 
66
- warning jq: allow_not_action_filter +
75
+ warning id: 'W16',
76
+ jq: allow_not_action_filter +
67
77
  "[#{resources_by_type('AWS::IAM::Policy')}|select(.Properties.PolicyDocument|allow_not_action)]|map(.LogicalResourceId)",
68
78
  message: 'IAM policy should not allow Allow+NotAction'
69
79
 
70
80
 
71
- warning jq: allow_not_action_filter +
81
+ warning id: 'W17',
82
+ jq: allow_not_action_filter +
72
83
  "[#{resources_by_type('AWS::IAM::ManagedPolicy')}|select(.Properties.PolicyDocument|allow_not_action)]|map(.LogicalResourceId)",
73
84
  message: 'IAM managed policy should not allow Allow+NotAction'
74
85
 
75
- warning jq: allow_not_action_filter +
86
+ warning id: 'W18',
87
+ jq: allow_not_action_filter +
76
88
  "[#{resources_by_type('AWS::SQS::QueuePolicy')}|select(.Properties.PolicyDocument|allow_not_action)]|map(.LogicalResourceId)",
77
89
  message: 'SQS Queue policy should not allow Allow+NotAction'
78
90
 
79
- warning jq: allow_not_action_filter +
91
+ warning id: 'W19',
92
+ jq: allow_not_action_filter +
80
93
  "[#{resources_by_type('AWS::SNS::TopicPolicy')}|select(.Properties.PolicyDocument|allow_not_action)]|map(.LogicalResourceId)",
81
94
  message: 'SNS Topic policy should not allow Allow+NotAction'
82
95
 
83
- warning jq: allow_not_action_filter +
96
+ warning id: 'W20',
97
+ jq: allow_not_action_filter +
84
98
  "[#{resources_by_type('AWS::S3::BucketPolicy')}|select(.Properties.PolicyDocument|allow_not_action)]|map(.LogicalResourceId)",
85
99
  message: 'S3 Bucket policy should not allow Allow+NotAction'
86
100
 
@@ -92,17 +106,20 @@ def allow_not_resource:
92
106
  end;
93
107
  END
94
108
 
95
- warning jq: allow_not_resource_filter +
109
+ warning id: 'W21',
110
+ jq: allow_not_resource_filter +
96
111
  "[#{resources_by_type('AWS::IAM::Role')}|select(.Properties.Policies !=null)|select(.Properties.Policies[].PolicyDocument|allow_not_resource)]|map(.LogicalResourceId)",
97
112
  message: 'IAM role should not allow Allow+NotResource'
98
113
 
99
114
 
100
- warning jq: allow_not_resource_filter +
115
+ warning id: 'W22',
116
+ jq: allow_not_resource_filter +
101
117
  "[#{resources_by_type('AWS::IAM::Policy')}|select(.Properties.PolicyDocument|allow_not_resource)]|map(.LogicalResourceId)",
102
118
  message: 'IAM policy should not allow Allow+NotResource'
103
119
 
104
120
 
105
- warning jq: allow_not_resource_filter +
121
+ warning id: 'W23',
122
+ jq: allow_not_resource_filter +
106
123
  "[#{resources_by_type('AWS::IAM::ManagedPolicy')}|select(.Properties.PolicyDocument|allow_not_resource)]|map(.LogicalResourceId)",
107
124
  message: 'IAM managed policy should not allow Allow+NotResource'
108
125
 
@@ -115,18 +132,22 @@ def allow_not_principal:
115
132
  end;
116
133
  END
117
134
 
118
- violation jq: allow_not_principal_filter +
135
+ violation id: 'F6',
136
+ jq: allow_not_principal_filter +
119
137
  "[#{resources_by_type('AWS::IAM::Role')}|select(.Properties.AssumeRolePolicyDocument|allow_not_principal)]|map(.LogicalResourceId)",
120
138
  message: 'IAM role should not allow Allow+NotPrincipal in its trust policy'
121
139
 
122
- violation jq: allow_not_principal_filter +
140
+ violation id: 'F7',
141
+ jq: allow_not_principal_filter +
123
142
  "[#{resources_by_type('AWS::SQS::QueuePolicy')}|select(.Properties.PolicyDocument|allow_not_principal)]|map(.LogicalResourceId)",
124
143
  message: 'SQS Queue policy should not allow Allow+NotPrincipal'
125
144
 
126
- violation jq: allow_not_principal_filter +
145
+ violation id: 'F8',
146
+ jq: allow_not_principal_filter +
127
147
  "[#{resources_by_type('AWS::SNS::TopicPolicy')}|select(.Properties.PolicyDocument|allow_not_principal)]|map(.LogicalResourceId)",
128
148
  message: 'SNS Topic policy should not allow Allow+NotPrincipal'
129
149
 
130
- violation jq: allow_not_principal_filter +
150
+ violation id: 'F9',
151
+ jq: allow_not_principal_filter +
131
152
  "[#{resources_by_type('AWS::S3::BucketPolicy')}|select(.Properties.PolicyDocument|allow_not_principal)]|map(.LogicalResourceId)",
132
153
  message: 'S3 Bucket policy should not allow Allow+NotPrincipal'
@@ -1,12 +1,15 @@
1
- violation jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::IAM::User")|'\
1
+ violation id: 'F10',
2
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::IAM::User")|'\
2
3
  'select(.Properties.Policies|length > 0)]|map(.LogicalResourceId) ',
3
4
  message: 'IAM user should not have any directly attached policies. Should be on group'
4
5
 
5
- violation jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::IAM::Policy")|'\
6
+ violation id: 'F11',
7
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::IAM::Policy")|'\
6
8
  'select(.Properties.Users|length > 0)]|map(.LogicalResourceId) ',
7
9
  message: 'IAM policy should not apply directly to users. Should be on group'
8
10
 
9
- violation jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::IAM::ManagedPolicy")|'\
11
+ violation id: 'F12',
12
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::IAM::ManagedPolicy")|'\
10
13
  'select(.Properties.Users|length > 0)]|map(.LogicalResourceId) ',
11
14
  message: 'IAM managed policy should not apply directly to users. Should be on group'
12
15
 
@@ -1,7 +1,9 @@
1
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::Lambda::Permission")|'\
1
+ warning id: 'W24',
2
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::Lambda::Permission")|'\
2
3
  'select(.Properties.Action != "lambda:InvokeFunction")]|map(.LogicalResourceId) ',
3
4
  message: 'Lambda permission beside InvokeFunction might not be what you want? Not sure!?'
4
5
 
5
- violation jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::Lambda::Permission")|'\
6
+ violation id: 'F13',
7
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::Lambda::Permission")|'\
6
8
  'select(.Properties.Principal == "*")]|map(.LogicalResourceId) ',
7
9
  message: 'Lambda permission principal should not be wildcard'
@@ -1,7 +1,9 @@
1
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::ElasticLoadBalancing::LoadBalancer")|'\
1
+ warning id: 'W25',
2
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::ElasticLoadBalancing::LoadBalancer")|'\
2
3
  'select(.Properties.AccessLoggingPolicy == null)]|map(.LogicalResourceId) ',
3
4
  message: 'Elastic Load Balancer should have access logging configured'
4
5
 
5
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::ElasticLoadBalancing::LoadBalancer")|'\
6
+ warning id: 'W26',
7
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::ElasticLoadBalancing::LoadBalancer")|'\
6
8
  'select(.Properties.AccessLoggingPolicy?.Enabled == false)]|map(.LogicalResourceId) ',
7
9
  message: 'Elastic Load Balancer should have access logging enabled'
@@ -1,4 +1,5 @@
1
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | '\
1
+ warning id: 'W27',
2
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | '\
2
3
  'select(.Type == "AWS::EC2::SecurityGroup")| '\
3
4
  'if (.Properties.SecurityGroupIngress|type == "object") '\
4
5
  'then select(.Properties.SecurityGroupIngress.ToPort != .Properties.SecurityGroupIngress.FromPort) '\
@@ -10,10 +11,12 @@ warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | '\
10
11
  ']|map(.LogicalResourceId)',
11
12
  message: 'Security Groups found ingress with port range instead of just a single port'
12
13
 
13
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroupIngress")|select(.Properties.ToPort != .Properties.FromPort)]|map(.LogicalResourceId)',
14
+ warning id: 'W28',
15
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroupIngress")|select(.Properties.ToPort != .Properties.FromPort)]|map(.LogicalResourceId)',
14
16
  message: 'Security Group ingress with port range instead of just a single port'
15
17
 
16
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | '\
18
+ warning id: 'W29',
19
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | '\
17
20
  'select(.Type == "AWS::EC2::SecurityGroup")| '\
18
21
  'if (.Properties.SecurityGroupEgress|type == "object") '\
19
22
  'then select(.Properties.SecurityGroupEgress.ToPort != .Properties.SecurityGroupEgress.FromPort) '\
@@ -25,5 +28,6 @@ warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | '\
25
28
  ']|map(.LogicalResourceId)',
26
29
  message: 'Security Groups found egress with port range instead of just a single port'
27
30
 
28
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroupEgress")|select(.Properties.ToPort != .Properties.FromPort)]|map(.LogicalResourceId)',
31
+ warning id: 'W30',
32
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroupEgress")|select(.Properties.ToPort != .Properties.FromPort)]|map(.LogicalResourceId)',
29
33
  message: 'Security Group egress with port range instead of just a single port'
@@ -1,8 +1,10 @@
1
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::S3::Bucket")|'\
1
+ warning id: 'W31',
2
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::S3::Bucket")|'\
2
3
  'select(.Properties.AccessControl? == "PublicRead")]|map(.LogicalResourceId) ',
3
4
  message: 'S3 Bucket likely should not have a public read acl'
4
5
 
5
- violation jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::S3::Bucket")|'\
6
+ violation id: 'F14',
7
+ jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::S3::Bucket")|'\
6
8
  'select(.Properties.AccessControl? == "PublicReadWrite")]|map(.LogicalResourceId) ',
7
9
  message: 'S3 Bucket should not have a public read-write acl'
8
10
 
@@ -33,14 +35,17 @@ def s3_wildcard_aws_principal:
33
35
  END
34
36
 
35
37
 
36
- violation jq: s3_wildcard_action_filter +
38
+ violation id: 'F15',
39
+ jq: s3_wildcard_action_filter +
37
40
  "[#{resources_by_type('AWS::S3::BucketPolicy')}|select(.Properties.PolicyDocument|s3_wildcard_action)]|map(.LogicalResourceId) ",
38
41
  message: 'S3 Bucket policy should not allow * action'
39
42
 
40
- violation jq: s3_wildcard_principal_filter +
43
+ violation id: 'F16',
44
+ jq: s3_wildcard_principal_filter +
41
45
  "[#{resources_by_type('AWS::S3::BucketPolicy')}|select(.Properties.PolicyDocument|s3_wildcard_principal)]|map(.LogicalResourceId) ",
42
46
  message: 'S3 Bucket policy should not allow * principal'
43
47
 
44
- violation jq: s3_wildcard_aws_principal_filter +
48
+ violation id: 'F17',
49
+ jq: s3_wildcard_aws_principal_filter +
45
50
  "[#{resources_by_type('AWS::S3::BucketPolicy')}|select(.Properties.PolicyDocument|s3_wildcard_aws_principal)]|map(.LogicalResourceId) ",
46
51
  message: 'S3 Bucket policy should not allow * AWS principal'
@@ -18,10 +18,12 @@ END
18
18
 
19
19
  #sns action wildcard doesnt seem to be accepted by sns so dont sweat it
20
20
 
21
- violation jq: sns_wildcard_principal_filter +
21
+ violation id: 'F18',
22
+ jq: sns_wildcard_principal_filter +
22
23
  "[#{resources_by_type('AWS::SNS::TopicPolicy')}|select(.Properties.PolicyDocument|sns_wildcard_principal)]|map(.LogicalResourceId) ",
23
24
  message: 'SNS topic policy should not allow * principal'
24
25
 
25
- violation jq: sns_wildcard_aws_principal_filter +
26
+ violation id: 'F19',
27
+ jq: sns_wildcard_aws_principal_filter +
26
28
  "[#{resources_by_type('AWS::SNS::TopicPolicy')}|select(.Properties.PolicyDocument|sns_wildcard_aws_principal)]|map(.LogicalResourceId) ",
27
29
  message: 'SNS topic policy should not allow * AWS principal'
@@ -14,10 +14,12 @@ def sqs_wildcard_principal:
14
14
  end;
15
15
  END
16
16
 
17
- violation jq: sqs_wildcard_action_filter +
17
+ violation id: 'F20',
18
+ jq: sqs_wildcard_action_filter +
18
19
  "[#{resources_by_type('AWS::SQS::QueuePolicy')}|select(.Properties.PolicyDocument|sqs_wildcard_action)]|map(.LogicalResourceId) ",
19
20
  message: 'SQS Queue policy should not allow * action'
20
21
 
21
- violation jq: sqs_wildcard_principal_filter +
22
+ violation id: 'F21',
23
+ jq: sqs_wildcard_principal_filter +
22
24
  "[#{resources_by_type('AWS::SQS::QueuePolicy')}|select(.Properties.PolicyDocument|sqs_wildcard_principal)]|map(.LogicalResourceId) ",
23
25
  message: 'SQS Queue policy should not allow * principal'
data/lib/profile.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'set'
2
+
3
+ class Profile
4
+
5
+ attr_reader :rule_ids
6
+
7
+ def initialize
8
+ @rule_ids = Set.new
9
+ end
10
+
11
+ def add_rule(rule_id)
12
+ @rule_ids << rule_id
13
+ end
14
+
15
+ def execute_rule?(rule_id)
16
+ @rule_ids.include? rule_id
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ require_relative 'profile'
2
+
3
+ class ProfileLoader
4
+
5
+ def initialize(rules_registry)
6
+ @rules_registry = rules_registry
7
+ end
8
+
9
+ def load(profile_definition:)
10
+
11
+ if profile_definition.nil? or profile_definition.strip == ''
12
+ raise 'Empty profile'
13
+ end
14
+
15
+ new_profile = Profile.new
16
+
17
+ profile_definition.each_line do |line|
18
+ rule_id = line.chomp
19
+ if @rules_registry.by_id(rule_id) == []
20
+ raise "#{rule_id} is not a legal rule identifier"
21
+ else
22
+ new_profile.add_rule rule_id
23
+ end
24
+ end
25
+ new_profile
26
+ end
27
+ end
@@ -9,7 +9,7 @@ class SimpleStdoutResults
9
9
  (1..60).each { print '-' }
10
10
 
11
11
  result[:file_results][:violations].each do |violation|
12
- message message_type: violation.type,
12
+ message message_type: "#{violation.type} #{violation.id}",
13
13
  message: violation.message,
14
14
  logical_resource_ids: violation.logical_resource_ids,
15
15
  violating_code: violation.violating_code
data/lib/rule.rb CHANGED
@@ -16,8 +16,10 @@ module Rule
16
16
  "#{resources}| select(.Type == \"#{resource}\")"
17
17
  end
18
18
 
19
- def warning(jq:, message:)
20
- @warning_registry << message
19
+ def warning(id:, jq:, message:)
20
+ warning_def = @rule_registry.definition(id: id,
21
+ type: Violation::WARNING,
22
+ message: message)
21
23
 
22
24
  return if @stop_processing
23
25
 
@@ -30,50 +32,57 @@ module Rule
30
32
  resource_ids = parse_logical_resource_ids(stdout)
31
33
  new_warnings = resource_ids.size
32
34
  if result == 0 and new_warnings > 0
33
- add_violation(type: Violation::WARNING,
35
+ add_violation(id: warning_def.id,
36
+ type: Violation::WARNING,
34
37
  message: message,
35
38
  logical_resource_ids: resource_ids)
36
39
  end
37
40
  end
38
41
 
39
- def raw_fatal_assertion(jq:, message:)
40
- failing_rule(jq_expression: jq,
42
+ def raw_fatal_assertion(id:, jq:, message:)
43
+ failing_rule(id: id,
44
+ jq_expression: jq,
41
45
  fail_if_found: false,
42
46
  fatal: true,
43
47
  message: message,
44
48
  raw: true)
45
49
  end
46
50
 
47
- def fatal_assertion(jq:, message:)
48
- failing_rule(jq_expression: jq,
51
+ def fatal_assertion(id:, jq:, message:)
52
+ failing_rule(id: id,
53
+ jq_expression: jq,
49
54
  fail_if_found: false,
50
55
  fatal: true,
51
56
  message: message)
52
57
  end
53
58
 
54
- def raw_fatal_violation(jq:, message:)
55
- failing_rule(jq_expression: jq,
59
+ def raw_fatal_violation(id:, jq:, message:)
60
+ failing_rule(id: id,
61
+ jq_expression: jq,
56
62
  fail_if_found: true,
57
63
  fatal: true,
58
64
  message: message,
59
65
  raw: true)
60
66
  end
61
67
 
62
- def fatal_violation(jq:, message:)
63
- failing_rule(jq_expression: jq,
68
+ def fatal_violation(id:, jq:, message:)
69
+ failing_rule(id: id,
70
+ jq_expression: jq,
64
71
  fail_if_found: true,
65
72
  fatal: true,
66
73
  message: message)
67
74
  end
68
75
 
69
- def violation(jq:, message:)
70
- failing_rule(jq_expression: jq,
76
+ def violation(id:, jq:, message:)
77
+ failing_rule(id: id,
78
+ jq_expression: jq,
71
79
  fail_if_found: true,
72
80
  message: message)
73
81
  end
74
82
 
75
- def assertion(jq:, message:)
76
- failing_rule(jq_expression: jq,
83
+ def assertion(id:, jq:, message:)
84
+ failing_rule(id: id,
85
+ jq_expression: jq,
77
86
  fail_if_found: false,
78
87
  message: message)
79
88
  end
@@ -108,11 +117,17 @@ module Rule
108
117
  end
109
118
  end
110
119
 
111
- def add_violation(type:,
120
+ # this is to record a violation as opposed to "registering" a violation
121
+ #
122
+ # not super keen on this looking at it after the fact.... @violations
123
+ # will have to live in the object this Rule is mixed into i.e. CfnNag
124
+ def add_violation(id:,
125
+ type:,
112
126
  message:,
113
127
  logical_resource_ids: nil,
114
128
  violating_code: nil)
115
- violation = Violation.new(type: type,
129
+ violation = Violation.new(id: id,
130
+ type: type,
116
131
  message: message,
117
132
  logical_resource_ids: logical_resource_ids,
118
133
  violating_code: violating_code)
@@ -135,12 +150,17 @@ module Rule
135
150
  # failure count by 1
136
151
  #
137
152
  # fatal: if true, any match of the rule causes immediate shutdown to avoid more complicated downstream error checking
138
- def failing_rule(jq_expression:,
153
+ def failing_rule(id:,
154
+ jq_expression:,
139
155
  fail_if_found:,
140
156
  message:,
141
157
  fatal: false,
142
158
  raw: false)
143
- @violation_registry << message
159
+
160
+ fail_def = @rule_registry.definition(id: id,
161
+ type: Violation::FAILING_VIOLATION,
162
+ message: message)
163
+
144
164
  return if @stop_processing
145
165
 
146
166
  Logging.logger['log'].debug jq_expression
@@ -152,7 +172,8 @@ module Rule
152
172
  (not fail_if_found and result != 0)
153
173
 
154
174
  if raw
155
- add_violation(type: Violation::FAILING_VIOLATION,
175
+ add_violation(id: fail_def.id,
176
+ type: Violation::FAILING_VIOLATION,
156
177
  message: message,
157
178
  violating_code: stdout)
158
179
 
@@ -163,7 +184,8 @@ module Rule
163
184
  resource_ids = parse_logical_resource_ids(stdout)
164
185
 
165
186
  if resource_ids.size > 0
166
- add_violation(type: Violation::FAILING_VIOLATION,
187
+ add_violation(id: fail_def.id,
188
+ type: Violation::FAILING_VIOLATION,
167
189
  message: message,
168
190
  logical_resource_ids: resource_ids)
169
191
 
@@ -0,0 +1,45 @@
1
+ require_relative 'violation'
2
+
3
+ class RuleRegistry
4
+
5
+ attr_reader :rules
6
+
7
+ def initialize
8
+ @rules = []
9
+ end
10
+
11
+ def definition(id:,
12
+ type:,
13
+ message:)
14
+ violation_def = Violation.new(id: id,
15
+ type: type,
16
+ message: message)
17
+ existing_def = @rules.find { |definition| definition == violation_def }
18
+
19
+ if existing_def.nil?
20
+ add_rule violation_def
21
+ else
22
+ existing_def
23
+ end
24
+ end
25
+
26
+ # FATAL applies to multiple rules
27
+ def by_id(id)
28
+ @rules.select { |rule| rule.id == id }
29
+ end
30
+
31
+ def warnings
32
+ @rules.select { |rule| rule.type == Violation::WARNING }
33
+ end
34
+
35
+ def failings
36
+ @rules.select { |rule| rule.type == Violation::FAILING_VIOLATION }
37
+ end
38
+
39
+ private
40
+
41
+ def add_rule(violation_def)
42
+ @rules << violation_def
43
+ violation_def
44
+ end
45
+ end
data/lib/violation.rb CHANGED
@@ -1,14 +1,17 @@
1
1
  require 'json'
2
+
2
3
  class Violation
3
4
  WARNING = 'WARN'
4
5
  FAILING_VIOLATION = 'FAIL'
5
6
 
6
- attr_reader :type, :message, :logical_resource_ids, :violating_code
7
+ attr_reader :id, :type, :message, :logical_resource_ids, :violating_code
7
8
 
8
- def initialize(type:,
9
+ def initialize(id:,
10
+ type:,
9
11
  message:,
10
12
  logical_resource_ids: nil,
11
13
  violating_code: nil)
14
+ @id = id
12
15
  @type = type
13
16
  @message = message
14
17
  @logical_resource_ids = logical_resource_ids
@@ -19,11 +22,12 @@ class Violation
19
22
  end
20
23
 
21
24
  def to_s
22
- puts "#{@type} #{@message} #{@logical_resource_ids} #{@violating_code}"
25
+ puts "#{@id} #{@type} #{@message} #{@logical_resource_ids} #{@violating_code}"
23
26
  end
24
27
 
25
28
  def to_h
26
29
  {
30
+ id: @id,
27
31
  type: @type,
28
32
  message: @message,
29
33
  logical_resource_ids: @logical_resource_ids,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfn-nag
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.34
4
+ version: 0.0.35
5
5
  platform: ruby
6
6
  authors:
7
7
  - someguy
@@ -73,9 +73,12 @@ files:
73
73
  - lib/model/s3_bucket_policy.rb
74
74
  - lib/model/s3_bucket_policy_parser.rb
75
75
  - lib/model/security_group_parser.rb
76
+ - lib/profile.rb
77
+ - lib/profile_loader.rb
76
78
  - lib/result_view/json_results.rb
77
79
  - lib/result_view/simple_stdout_results.rb
78
80
  - lib/rule.rb
81
+ - lib/rule_registry.rb
79
82
  - lib/violation.rb
80
83
  homepage: https://github.com/stelligent/cfn_nag
81
84
  licenses: