cfn-nag 0.0.16 → 0.0.17

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: 3c770e0365059a4da534e9dd5c6cf5ccd9e7bdfc
4
- data.tar.gz: 8f930ab8307b6248ae4940a17f8d9b4ed85b4e92
3
+ metadata.gz: d3284f4eba9d2cab9d7790dab39273f9401fbb09
4
+ data.tar.gz: e715f4800f39198bd95bc1fb6044ce1dd64644d6
5
5
  SHA512:
6
- metadata.gz: cd100b68be2758194ecd45141e821fcbc6845e4f56204dbee2444913524714ecd317a8c41af36cca9fc35aaa59dff5488b3946d819b1dabaaaa9cca562a078d2
7
- data.tar.gz: 464fad5e55f3d090078ed7a520304fc392d3f2106497b01bfaa2c6e4440fd3d328de5b4022dcdf8639e4e83cd4324894ceb5dac754d212e124dbf32e85a6edd6
6
+ metadata.gz: 06b61112e6e6cff426fcd6e07af0c0aa75f85f38914816bc1df48286e4aec04d76d9000d61202c9fae3a138e50d55c9af8ca0da56971f07644bdfde4b80f92a8
7
+ data.tar.gz: 43147a231964233735ee827ee0f1c9ec4894f6725904e95b137b3e8205181213d5ea5698e75ce704fd695025015e9a31467d65d1e14d0580587d79dfbdc75071
data/bin/cfn_nag_rules ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'cfn_nag'
3
+
4
+ CfnNag.new.dump_rules
data/lib/cfn_nag.rb CHANGED
@@ -4,10 +4,47 @@ require_relative 'custom_rules/user_missing_group'
4
4
  require_relative 'model/cfn_model'
5
5
  require_relative 'result_view/simple_stdout_results'
6
6
  require_relative 'result_view/json_results'
7
+ require 'tempfile'
7
8
 
8
9
  class CfnNag
9
10
  include Rule
10
11
 
12
+ def initialize
13
+ @warning_registry = []
14
+ @violation_registry = []
15
+ end
16
+
17
+ def dump_rules
18
+ dummy_cfn = <<-END
19
+ {
20
+ "Resources": {
21
+ "resource1": {
22
+ "Type" : "AWS::EC2::DHCPOptions",
23
+ "Properties": {
24
+ "DomainNameServers" : [ "10.0.0.1" ]
25
+ }
26
+ }
27
+ }
28
+ }
29
+ END
30
+
31
+ Tempfile.open('tempfile') do |dummy_cfn_template|
32
+ dummy_cfn_template.write dummy_cfn
33
+ dummy_cfn_template.rewind
34
+ audit_file(input_json_path: dummy_cfn_template.path)
35
+ end
36
+
37
+ custom_rule_registry.each do |rule_class|
38
+ @violation_registry << rule_class.new.rule_text
39
+ end
40
+
41
+ puts 'WARNING VIOLATIONS:'
42
+ puts @warning_registry.sort
43
+ puts
44
+ puts 'FAILING VIOLATIONS:'
45
+ puts @violation_registry.sort
46
+ end
47
+
11
48
  def audit(input_json_path:,
12
49
  output_format:'txt')
13
50
 
@@ -129,13 +166,16 @@ class CfnNag
129
166
 
130
167
  def custom_rules(input_json_path)
131
168
  cfn_model = CfnModel.new.parse(IO.read(input_json_path))
132
- rules = [
133
- SecurityGroupMissingEgressRule,
134
- UserMissingGroupRule
135
- ]
136
- rules.each do |rule_class|
169
+ custom_rule_registry.each do |rule_class|
137
170
  audit_result = rule_class.new.audit(cfn_model)
138
171
  @violations << audit_result unless audit_result.nil?
139
172
  end
140
173
  end
174
+
175
+ def custom_rule_registry
176
+ [
177
+ SecurityGroupMissingEgressRule,
178
+ UserMissingGroupRule
179
+ ]
180
+ end
141
181
  end
@@ -2,6 +2,10 @@ require_relative '../violation'
2
2
 
3
3
  class SecurityGroupMissingEgressRule
4
4
 
5
+ def rule_text
6
+ 'Missing egress rule means all traffic is allowed outbound. Make this explicit if it is desired configuration'
7
+ end
8
+
5
9
  def audit(cfn_model)
6
10
  logical_resource_ids = []
7
11
  cfn_model.security_groups.each do |security_group|
@@ -12,7 +16,7 @@ class SecurityGroupMissingEgressRule
12
16
 
13
17
  if logical_resource_ids.size > 0
14
18
  Violation.new(type: Violation::FAILING_VIOLATION,
15
- message: 'Missing egress rule means all traffic is allowed outbound. Make this explicit if it is desired configuration',
19
+ message: rule_text,
16
20
  logical_resource_ids: logical_resource_ids)
17
21
  else
18
22
  nil
@@ -2,6 +2,10 @@ require_relative '../violation'
2
2
 
3
3
  class UserMissingGroupRule
4
4
 
5
+ def rule_text
6
+ 'User is not assigned to a group'
7
+ end
8
+
5
9
  def audit(cfn_model)
6
10
  logical_resource_ids = []
7
11
  cfn_model.iam_users.each do |iam_user|
@@ -12,7 +16,7 @@ class UserMissingGroupRule
12
16
 
13
17
  if logical_resource_ids.size > 0
14
18
  Violation.new(type: Violation::FAILING_VIOLATION,
15
- message: 'User is not assigned to a group',
19
+ message: rule_text,
16
20
  logical_resource_ids: logical_resource_ids)
17
21
  else
18
22
  nil
@@ -1,5 +1,5 @@
1
1
  raw_fatal_assertion jq: '.Resources|length > 0',
2
- message: 'Must have at least 1 resource'
2
+ message: 'A Cloudformation template must have at least 1 resource'
3
3
 
4
4
 
5
5
  %w(
@@ -28,19 +28,34 @@ warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | selec
28
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)',
29
29
  message: 'Security Group Standalone Ingress cidr found that is not /32'
30
30
 
31
-
32
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupIngress|type == "object"))|select(.Properties.SecurityGroupIngress.CidrIp|type == "string")|select(.Properties.SecurityGroupIngress.CidrIp | test("^\\\d{1,3}\\\.\\\d{1,3}\\\.\\\d{1,3}\\\.\\\d{1,3}/(?!32)$") )]|map(.LogicalResourceId)',
33
- message: 'Security Groups found with cidr that is not /32'
34
-
35
-
36
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupIngress|type == "array"))|select(.Properties.SecurityGroupIngress[].CidrIp|type == "string")|select(.Properties.SecurityGroupIngress[].CidrIp | if .|type=="string" then test("^\\\d{1,3}\\\.\\\d{1,3}\\\.\\\d{1,3}\\\.\\\d{1,3}/(?!32)$") else false end)]|map(.LogicalResourceId)',
31
+ non_32_cidr_jq_expression = <<END
32
+ [.Resources |
33
+ with_entries(.value.LogicalResourceId = .key)[] |
34
+ select(.Type == "AWS::EC2::SecurityGroup") |
35
+ if (.Properties.SecurityGroupIngress|type == "object")
36
+ then (
37
+ select(.Properties.SecurityGroupIngress.CidrIp|type == "string")|
38
+ select(.Properties.SecurityGroupIngress.CidrIp|test("^\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}/(?!32)$"))
39
+ )
40
+ else (
41
+ if (.Properties.SecurityGroupIngress|type == "array")
42
+ then (
43
+ select(.Properties.SecurityGroupIngress[].CidrIp|type == "string")|
44
+ select(.Properties.SecurityGroupIngress[].CidrIp |
45
+ (
46
+ if (.|type=="string")
47
+ then test("^\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}\\\\.\\\\d{1,3}/(?!32)$")
48
+ else empty
49
+ end
50
+ )
51
+ )
52
+ )
53
+ else empty
54
+ end
55
+ )
56
+ end
57
+ ]|map(.LogicalResourceId)
58
+ END
59
+
60
+ warning jq: non_32_cidr_jq_expression,
37
61
  message: 'Security Groups found with cidr that is not /32'
38
-
39
-
40
- #for inline, this covers it, but with an externalized egress rule... the expression gets real evil
41
- #i guess the ideal would be to do a join of ingress and egress rules with the parent sg
42
- #but it gets real hairy with FnGetAtt for GroupId and all that.... think it best to
43
- #write some imperative code in custom rules to take care of things
44
- # violation '.Resources[]|select(.Type == "AWS::EC2::SecurityGroup")|select(.Properties.SecurityGroupEgress == null or .Properties.SecurityGroupEgress.length? == 0)' do |security_groups|
45
- # puts "Security Groups found without egress json_rules: #{security_groups}"
46
- # end
@@ -55,7 +55,7 @@ END
55
55
 
56
56
  warning jq: allow_not_action_filter +
57
57
  "[#{resources_by_type('AWS::IAM::Role')}|select(.Properties.AssumeRolePolicyDocument|allow_not_action)]|map(.LogicalResourceId)",
58
- message: 'IAM role should not allow Allow+NotAction'
58
+ message: 'IAM role should not allow Allow+NotAction on trust permissinos'
59
59
 
60
60
 
61
61
  warning jq: allow_not_action_filter +
@@ -8,5 +8,5 @@ violation jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | sel
8
8
 
9
9
  violation jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::IAM::ManagedPolicy")|'\
10
10
  'select(.Properties.Users|length > 0)]|map(.LogicalResourceId) ',
11
- message: 'IAM policy should not apply directly to users. Should be on group'
11
+ message: 'IAM managed policy should not apply directly to users. Should be on group'
12
12
 
@@ -1,17 +1,29 @@
1
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupIngress|type == "object"))|select(.Properties.SecurityGroupIngress.ToPort != .Properties.SecurityGroupIngress.FromPort)]|map(.LogicalResourceId)',
1
+ warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | '\
2
+ 'select(.Type == "AWS::EC2::SecurityGroup")| '\
3
+ 'if (.Properties.SecurityGroupIngress|type == "object") '\
4
+ 'then select(.Properties.SecurityGroupIngress.ToPort != .Properties.SecurityGroupIngress.FromPort) '\
5
+ 'else if (.Properties.SecurityGroupIngress|type == "array") '\
6
+ ' then select([.Properties.SecurityGroupIngress[].ToPort] != [.Properties.SecurityGroupIngress[].FromPort]) '\
7
+ ' else empty '\
8
+ ' end '\
9
+ 'end '\
10
+ ']|map(.LogicalResourceId)',
2
11
  message: 'Security Groups found ingress with port range instead of just a single port'
3
12
 
4
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupIngress|type == "array"))|select([.Properties.SecurityGroupIngress[].ToPort] != [.Properties.SecurityGroupIngress[].FromPort])]|map(.LogicalResourceId)',
5
- message: 'Security Groups found ingress with port range instead of just a single port'
6
-
7
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupEgress|type == "object"))|select(.Properties.SecurityGroupEgress.ToPort != .Properties.SecurityGroupEgress.FromPort)]|map(.LogicalResourceId)',
8
- message: 'Security Groups found egress with port range instead of just a single port'
13
+ warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroupIngress")|select(.Properties.ToPort != .Properties.FromPort)]|map(.LogicalResourceId)',
14
+ message: 'Security Group ingress with port range instead of just a single port'
9
15
 
10
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroup" and (.Properties.SecurityGroupEgress|type == "array"))|select([.Properties.SecurityGroupEgress[].ToPort] != [.Properties.SecurityGroupEgress[].FromPort])]|map(.LogicalResourceId)',
16
+ warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | '\
17
+ 'select(.Type == "AWS::EC2::SecurityGroup")| '\
18
+ 'if (.Properties.SecurityGroupEgress|type == "object") '\
19
+ 'then select(.Properties.SecurityGroupEgress.ToPort != .Properties.SecurityGroupEgress.FromPort) '\
20
+ 'else if (.Properties.SecurityGroupEgress|type == "array") '\
21
+ ' then select([.Properties.SecurityGroupEgress[].ToPort] != [.Properties.SecurityGroupEgress[].FromPort]) '\
22
+ ' else empty '\
23
+ ' end '\
24
+ 'end'\
25
+ ']|map(.LogicalResourceId)',
11
26
  message: 'Security Groups found egress with port range instead of just a single port'
12
27
 
13
- warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroupIngress")|select(.Properties.ToPort != .Properties.FromPort)]|map(.LogicalResourceId)',
14
- message: 'Security Groups found ingress with port range instead of just a single port'
15
-
16
28
  warning jq: '[.Resources|with_entries(.value.LogicalResourceId = .key)[] | select(.Type == "AWS::EC2::SecurityGroupEgress")|select(.Properties.ToPort != .Properties.FromPort)]|map(.LogicalResourceId)',
17
- message: 'Security Groups found egress with port range instead of just a single port'
29
+ message: 'Security Group egress with port range instead of just a single port'
data/lib/rule.rb CHANGED
@@ -17,13 +17,15 @@ module Rule
17
17
  end
18
18
 
19
19
  def warning(jq:, message:)
20
+ @warning_registry << message
21
+
20
22
  return if @stop_processing
21
23
 
22
24
  Logging.logger['log'].debug jq
23
25
 
24
26
  stdout = jq_command(@input_json_path, jq)
25
27
  result = $?.exitstatus
26
- scrape_jq_output_for_error(stdout)
28
+ scrape_jq_output_for_error(jq, stdout)
27
29
 
28
30
  resource_ids = parse_logical_resource_ids(stdout)
29
31
  new_warnings = resource_ids.size
@@ -39,7 +41,6 @@ module Rule
39
41
  fail_if_found: false,
40
42
  fatal: true,
41
43
  message: message,
42
- message_type: Violation::FAILING_VIOLATION,
43
44
  raw: true)
44
45
  end
45
46
 
@@ -47,8 +48,7 @@ module Rule
47
48
  failing_rule(jq_expression: jq,
48
49
  fail_if_found: false,
49
50
  fatal: true,
50
- message: message,
51
- message_type: Violation::FAILING_VIOLATION)
51
+ message: message)
52
52
  end
53
53
 
54
54
  def raw_fatal_violation(jq:, message:)
@@ -56,7 +56,6 @@ module Rule
56
56
  fail_if_found: true,
57
57
  fatal: true,
58
58
  message: message,
59
- message_type: Violation::FAILING_VIOLATION,
60
59
  raw: true)
61
60
  end
62
61
 
@@ -64,22 +63,19 @@ module Rule
64
63
  failing_rule(jq_expression: jq,
65
64
  fail_if_found: true,
66
65
  fatal: true,
67
- message: message,
68
- message_type: Violation::FAILING_VIOLATION)
66
+ message: message)
69
67
  end
70
68
 
71
69
  def violation(jq:, message:)
72
70
  failing_rule(jq_expression: jq,
73
71
  fail_if_found: true,
74
- message: message,
75
- message_type: Violation::FAILING_VIOLATION)
72
+ message: message)
76
73
  end
77
74
 
78
75
  def assertion(jq:, message:)
79
76
  failing_rule(jq_expression: jq,
80
77
  fail_if_found: false,
81
- message: message,
82
- message_type: Violation::FAILING_VIOLATION)
78
+ message: message)
83
79
  end
84
80
 
85
81
  def self.empty?(array)
@@ -129,8 +125,8 @@ module Rule
129
125
  JSON.load(stdout)
130
126
  end
131
127
 
132
- def scrape_jq_output_for_error(stdout)
133
- fail 'json rule is likely not complete' if stdout.match /jq: error/
128
+ def scrape_jq_output_for_error(command, stdout)
129
+ fail "jq rule is likely not correct: #{command}\n\n#{stdout}" if stdout.include? 'jq: error'
134
130
  end
135
131
 
136
132
  # fail_if_found: this is false for an assertion, true for a violation. either way this rule ups the "failure" count
@@ -142,21 +138,21 @@ module Rule
142
138
  def failing_rule(jq_expression:,
143
139
  fail_if_found:,
144
140
  message:,
145
- message_type:,
146
141
  fatal: false,
147
142
  raw: false)
143
+ @violation_registry << message
148
144
  return if @stop_processing
149
145
 
150
146
  Logging.logger['log'].debug jq_expression
151
147
 
152
148
  stdout = jq_command(@input_json_path, jq_expression)
153
149
  result = $?.exitstatus
154
- scrape_jq_output_for_error(stdout)
150
+ scrape_jq_output_for_error(jq_expression, stdout)
155
151
  if (fail_if_found and result == 0) or
156
152
  (not fail_if_found and result != 0)
157
153
 
158
154
  if raw
159
- add_violation(type: message_type,
155
+ add_violation(type: Violation::FAILING_VIOLATION,
160
156
  message: message,
161
157
  violating_code: stdout)
162
158
 
@@ -167,7 +163,7 @@ module Rule
167
163
  resource_ids = parse_logical_resource_ids(stdout)
168
164
 
169
165
  if resource_ids.size > 0
170
- add_violation(type: message_type,
166
+ add_violation(type: Violation::FAILING_VIOLATION,
171
167
  message: message,
172
168
  logical_resource_ids: resource_ids)
173
169
 
@@ -181,7 +177,7 @@ module Rule
181
177
 
182
178
  # the -e will return an exit code
183
179
  def jq_command(input_json_path, jq_expression)
184
- command = "cat #{input_json_path} | jq '#{jq_expression}' -e"
180
+ command = "cat #{input_json_path} | jq '#{jq_expression}' -e 2>&1"
185
181
 
186
182
  Logging.logger['log'].debug command
187
183
 
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.0.16
4
+ version: 0.0.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - someguy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-01 00:00:00.000000000 Z
11
+ date: 2016-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logging
@@ -42,10 +42,12 @@ description: Auditing tool for Cloudformation templates
42
42
  email:
43
43
  executables:
44
44
  - cfn_nag
45
+ - cfn_nag_rules
45
46
  extensions: []
46
47
  extra_rdoc_files: []
47
48
  files:
48
49
  - bin/cfn_nag
50
+ - bin/cfn_nag_rules
49
51
  - lib/cfn_nag.rb
50
52
  - lib/custom_rules/security_group_missing_egress.rb
51
53
  - lib/custom_rules/user_missing_group.rb