cfn-nag 0.0.34 → 0.0.35

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: 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: