cfn-nag 0.3.36 → 0.3.37
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cfn-nag/cfn_nag.rb +23 -15
- data/lib/cfn-nag/custom_rule_loader.rb +16 -10
- data/lib/cfn-nag/custom_rules/CloudFormationAuthenticationRule.rb +2 -1
- data/lib/cfn-nag/custom_rules/CloudFrontDistributionAccessLoggingRule.rb +5 -3
- data/lib/cfn-nag/custom_rules/EbsVolumeHasSseRule.rb +4 -3
- data/lib/cfn-nag/custom_rules/ElasticLoadBalancerAccessLoggingRule.rb +6 -3
- data/lib/cfn-nag/custom_rules/IamManagedPolicyNotActionRule.rb +5 -3
- data/lib/cfn-nag/custom_rules/IamManagedPolicyNotResourceRule.rb +5 -3
- data/lib/cfn-nag/custom_rules/IamManagedPolicyWildcardActionRule.rb +5 -3
- data/lib/cfn-nag/custom_rules/IamManagedPolicyWildcardResourceRule.rb +5 -3
- data/lib/cfn-nag/custom_rules/IamPolicyNotActionRule.rb +5 -3
- data/lib/cfn-nag/custom_rules/IamPolicyNotResourceRule.rb +5 -3
- data/lib/cfn-nag/custom_rules/SecurityGroupIngressOpenToWorldRule.rb +4 -2
- data/lib/cfn-nag/custom_rules/ebs_volumes_jmespath.rb +4 -1
- data/lib/cfn-nag/ip_addr.rb +12 -6
- data/lib/cfn-nag/profile_loader.rb +2 -1
- data/lib/cfn-nag/result_view/json_results.rb +2 -1
- data/lib/cfn-nag/rule_dumper.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 679f328149572244f9d7716508fb21ec61d51b82
|
4
|
+
data.tar.gz: 5fe1047c08e2b48a6e7370ffb65ac7aa877a0503
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5cf850041ccbb24904d252e21153c84a843bdfd05eec85fd87f2752ac1707b731b06ba81ffa1b5b81fea4bf77c71c209d4898f1ef6fb4d4b192dc92cd29a0ae6
|
7
|
+
data.tar.gz: 2d2ed30c52bdd934748b423982daba740ed52b427ee186d9c95ab7cda8996be33ff2d1410e81f8e2bc8cdf786dc630bff9bb43c572d7e49e2a089cf8147f0a59
|
data/lib/cfn-nag/cfn_nag.rb
CHANGED
@@ -14,10 +14,11 @@ class CfnNag
|
|
14
14
|
print_suppression: false,
|
15
15
|
isolate_custom_rule_exceptions: false)
|
16
16
|
@rule_directory = rule_directory
|
17
|
-
@custom_rule_loader = CustomRuleLoader.new(
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
@custom_rule_loader = CustomRuleLoader.new(
|
18
|
+
rule_directory: rule_directory, allow_suppression: allow_suppression,
|
19
|
+
print_suppression: print_suppression,
|
20
|
+
isolate_custom_rule_exceptions: isolate_custom_rule_exceptions
|
21
|
+
)
|
21
22
|
@profile_definition = profile_definition
|
22
23
|
end
|
23
24
|
|
@@ -26,10 +27,12 @@ class CfnNag
|
|
26
27
|
#
|
27
28
|
# Return an aggregate failure count (for exit code usage)
|
28
29
|
#
|
29
|
-
def audit_aggregate_across_files_and_render_results(
|
30
|
-
|
31
|
-
|
32
|
-
aggregate_results =
|
30
|
+
def audit_aggregate_across_files_and_render_results(
|
31
|
+
input_path:, output_format: 'txt', parameter_values_path: nil
|
32
|
+
)
|
33
|
+
aggregate_results = \
|
34
|
+
audit_aggregate_across_files input_path: input_path,
|
35
|
+
parameter_values_path: parameter_values_path
|
33
36
|
|
34
37
|
render_results(aggregate_results: aggregate_results,
|
35
38
|
output_format: output_format)
|
@@ -43,7 +46,8 @@ class CfnNag
|
|
43
46
|
# Given a file or directory path, return aggregate results
|
44
47
|
#
|
45
48
|
def audit_aggregate_across_files(input_path:, parameter_values_path: nil)
|
46
|
-
parameter_values_string =
|
49
|
+
parameter_values_string = \
|
50
|
+
parameter_values_path.nil? ? nil : IO.read(parameter_values_path)
|
47
51
|
templates = TemplateDiscovery.new.discover_templates(input_path)
|
48
52
|
aggregate_results = []
|
49
53
|
templates.each do |template|
|
@@ -59,7 +63,8 @@ class CfnNag
|
|
59
63
|
##
|
60
64
|
# Given cloudformation json/yml, run all the rules against it
|
61
65
|
#
|
62
|
-
# Optionally include JSON with Parameters key to substitute into
|
66
|
+
# Optionally include JSON with Parameters key to substitute into
|
67
|
+
# cfn_model.parameters
|
63
68
|
#
|
64
69
|
# Return a hash with failure count
|
65
70
|
#
|
@@ -68,7 +73,8 @@ class CfnNag
|
|
68
73
|
violations = []
|
69
74
|
|
70
75
|
begin
|
71
|
-
cfn_model = CfnParser.new.parse cloudformation_string,
|
76
|
+
cfn_model = CfnParser.new.parse cloudformation_string,
|
77
|
+
parameter_values_string
|
72
78
|
rescue Psych::SyntaxError, ParserError => parser_error
|
73
79
|
violations << Violation.new(id: 'FATAL',
|
74
80
|
type: Violation::FAILING_VIOLATION,
|
@@ -76,9 +82,10 @@ class CfnNag
|
|
76
82
|
stop_processing = true
|
77
83
|
end
|
78
84
|
|
79
|
-
|
80
|
-
|
81
|
-
|
85
|
+
unless stop_processing == true
|
86
|
+
violations += @custom_rule_loader.execute_custom_rules(cfn_model)
|
87
|
+
violations = filter_violations_by_profile violations
|
88
|
+
end
|
82
89
|
|
83
90
|
{
|
84
91
|
failure_count: Violation.count_failures(violations),
|
@@ -102,7 +109,8 @@ class CfnNag
|
|
102
109
|
def filter_violations_by_profile(violations)
|
103
110
|
profile = nil
|
104
111
|
unless @profile_definition.nil?
|
105
|
-
profile = ProfileLoader.new(@custom_rule_loader.rule_definitions)
|
112
|
+
profile = ProfileLoader.new(@custom_rule_loader.rule_definitions)
|
113
|
+
.load(profile_definition: @profile_definition)
|
106
114
|
end
|
107
115
|
|
108
116
|
violations.reject do |violation|
|
@@ -49,9 +49,9 @@ class CustomRuleLoader
|
|
49
49
|
|
50
50
|
discover_rule_classes(@rule_directory).each do |rule_class|
|
51
51
|
begin
|
52
|
-
filtered_cfn_model = cfn_model_with_suppressed_resources_removed
|
53
|
-
|
54
|
-
|
52
|
+
filtered_cfn_model = cfn_model_with_suppressed_resources_removed \
|
53
|
+
cfn_model: cfn_model, rule_id: rule_class.new.rule_id,
|
54
|
+
allow_suppression: @allow_suppression
|
55
55
|
audit_result = rule_class.new.audit(filtered_cfn_model)
|
56
56
|
violations << audit_result unless audit_result.nil?
|
57
57
|
rescue Exception => exception
|
@@ -76,7 +76,10 @@ class CustomRuleLoader
|
|
76
76
|
private
|
77
77
|
|
78
78
|
def rules_to_suppress(resource)
|
79
|
-
if resource.metadata &&
|
79
|
+
if resource.metadata &&
|
80
|
+
resource.metadata['cfn_nag'] &&
|
81
|
+
resource.metadata['cfn_nag']['rules_to_suppress']
|
82
|
+
|
80
83
|
resource.metadata['cfn_nag']['rules_to_suppress']
|
81
84
|
else
|
82
85
|
nil
|
@@ -102,7 +105,8 @@ class CustomRuleLoader
|
|
102
105
|
logical_resource_id = mangled_metadata.first
|
103
106
|
mangled_rules = mangled_metadata[1]
|
104
107
|
|
105
|
-
STDERR.puts "#{logical_resource_id} has missing cfn_nag
|
108
|
+
STDERR.puts "#{logical_resource_id} has missing cfn_nag " \
|
109
|
+
"suppression rule id: #{mangled_rules}"
|
106
110
|
end
|
107
111
|
end
|
108
112
|
|
@@ -112,7 +116,8 @@ class CustomRuleLoader
|
|
112
116
|
rule_to_suppress['id'] == rule_id
|
113
117
|
end
|
114
118
|
if found_suppression_rule && @print_suppression
|
115
|
-
STDERR.puts "Suppressing #{rule_id} on #{logical_resource_id}
|
119
|
+
STDERR.puts "Suppressing #{rule_id} on #{logical_resource_id} " \
|
120
|
+
"for reason: #{found_suppression_rule['reason']}"
|
116
121
|
end
|
117
122
|
!found_suppression_rule.nil?
|
118
123
|
end
|
@@ -136,9 +141,8 @@ class CustomRuleLoader
|
|
136
141
|
end
|
137
142
|
|
138
143
|
def validate_extra_rule_directory(rule_directory)
|
139
|
-
|
140
|
-
|
141
|
-
end
|
144
|
+
return true if rule_directory.nil? || File.directory?(rule_directory)
|
145
|
+
raise "Not a real directory #{rule_directory}"
|
142
146
|
end
|
143
147
|
|
144
148
|
def discover_rule_filenames(rule_directory)
|
@@ -172,7 +176,9 @@ class CustomRuleLoader
|
|
172
176
|
unless rule_directory.nil?
|
173
177
|
rule_filenames += Dir[File.join(rule_directory, '*jmespath.rb')].sort
|
174
178
|
end
|
175
|
-
rule_filenames += Dir[File.join(__dir__,
|
179
|
+
rule_filenames += Dir[File.join(__dir__,
|
180
|
+
'custom_rules',
|
181
|
+
'*jmespath.rb')].sort
|
176
182
|
Logging.logger['log'].debug "jmespath_filenames: #{rule_filenames}"
|
177
183
|
rule_filenames
|
178
184
|
end
|
@@ -3,7 +3,8 @@ require_relative 'base'
|
|
3
3
|
|
4
4
|
class CloudFormationAuthenticationRule < BaseRule
|
5
5
|
def rule_text
|
6
|
-
'Specifying credentials in the template itself
|
6
|
+
'Specifying credentials in the template itself ' \
|
7
|
+
'is probably not the safest thing'
|
7
8
|
end
|
8
9
|
|
9
10
|
def rule_type
|
@@ -15,9 +15,11 @@ class CloudFrontDistributionAccessLoggingRule < BaseRule
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def audit_impl(cfn_model)
|
18
|
-
violating_distributions =
|
19
|
-
|
20
|
-
|
18
|
+
violating_distributions = \
|
19
|
+
cfn_model.resources_by_type('AWS::CloudFront::Distribution')
|
20
|
+
.select do |distribution|
|
21
|
+
distribution.distributionConfig['Logging'].nil?
|
22
|
+
end
|
21
23
|
|
22
24
|
violating_distributions.map(&:logical_resource_id)
|
23
25
|
end
|
@@ -15,9 +15,10 @@ class EbsVolumeHasSseRule < BaseRule
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def audit_impl(cfn_model)
|
18
|
-
violating_volumes =
|
19
|
-
|
20
|
-
|
18
|
+
violating_volumes = \
|
19
|
+
cfn_model.resources_by_type('AWS::EC2::Volume').select do |volume|
|
20
|
+
volume.encrypted.nil? || volume.encrypted.to_s.downcase == 'false'
|
21
|
+
end
|
21
22
|
|
22
23
|
violating_volumes.map(&:logical_resource_id)
|
23
24
|
end
|
@@ -15,9 +15,12 @@ class ElasticLoadBalancerAccessLoggingRule < BaseRule
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def audit_impl(cfn_model)
|
18
|
-
violating_elbs =
|
19
|
-
|
20
|
-
|
18
|
+
violating_elbs = \
|
19
|
+
cfn_model.resources_by_type('AWS::ElasticLoadBalancing::LoadBalancer')
|
20
|
+
.select do |elb|
|
21
|
+
elb.accessLoggingPolicy.nil? ||
|
22
|
+
elb.accessLoggingPolicy['Enabled'] != true
|
23
|
+
end
|
21
24
|
|
22
25
|
violating_elbs.map(&:logical_resource_id)
|
23
26
|
end
|
@@ -15,9 +15,11 @@ class IamManagedPolicyNotActionRule < BaseRule
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def audit_impl(cfn_model)
|
18
|
-
violating_policies =
|
19
|
-
|
20
|
-
|
18
|
+
violating_policies = \
|
19
|
+
cfn_model.resources_by_type('AWS::IAM::ManagedPolicy')
|
20
|
+
.select do |policy|
|
21
|
+
!policy.policy_document.allows_not_action.empty?
|
22
|
+
end
|
21
23
|
|
22
24
|
violating_policies.map(&:logical_resource_id)
|
23
25
|
end
|
@@ -15,9 +15,11 @@ class IamManagedPolicyNotResourceRule < BaseRule
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def audit_impl(cfn_model)
|
18
|
-
violating_policies =
|
19
|
-
|
20
|
-
|
18
|
+
violating_policies = \
|
19
|
+
cfn_model.resources_by_type('AWS::IAM::ManagedPolicy')
|
20
|
+
.select do |policy|
|
21
|
+
!policy.policy_document.allows_not_resource.empty?
|
22
|
+
end
|
21
23
|
|
22
24
|
violating_policies.map(&:logical_resource_id)
|
23
25
|
end
|
@@ -15,9 +15,11 @@ class IamManagedPolicyWildcardActionRule < BaseRule
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def audit_impl(cfn_model)
|
18
|
-
violating_policies =
|
19
|
-
|
20
|
-
|
18
|
+
violating_policies = \
|
19
|
+
cfn_model.resources_by_type('AWS::IAM::ManagedPolicy')
|
20
|
+
.select do |policy|
|
21
|
+
!policy.policy_document.wildcard_allowed_actions.empty?
|
22
|
+
end
|
21
23
|
|
22
24
|
violating_policies.map(&:logical_resource_id)
|
23
25
|
end
|
@@ -15,9 +15,11 @@ class IamManagedPolicyWildcardResourceRule < BaseRule
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def audit_impl(cfn_model)
|
18
|
-
violating_policies =
|
19
|
-
|
20
|
-
|
18
|
+
violating_policies = \
|
19
|
+
cfn_model.resources_by_type('AWS::IAM::ManagedPolicy')
|
20
|
+
.select do |policy|
|
21
|
+
!policy.policy_document.wildcard_allowed_resources.empty?
|
22
|
+
end
|
21
23
|
|
22
24
|
violating_policies.map(&:logical_resource_id)
|
23
25
|
end
|
@@ -15,9 +15,11 @@ class IamPolicyNotActionRule < BaseRule
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def audit_impl(cfn_model)
|
18
|
-
violating_policies =
|
19
|
-
|
20
|
-
|
18
|
+
violating_policies = \
|
19
|
+
cfn_model.resources_by_type('AWS::IAM::Policy')
|
20
|
+
.select do |policy|
|
21
|
+
!policy.policy_document.allows_not_action.empty?
|
22
|
+
end
|
21
23
|
|
22
24
|
violating_policies.map(&:logical_resource_id)
|
23
25
|
end
|
@@ -15,9 +15,11 @@ class IamPolicyNotResourceRule < BaseRule
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def audit_impl(cfn_model)
|
18
|
-
violating_policies =
|
19
|
-
|
20
|
-
|
18
|
+
violating_policies = \
|
19
|
+
cfn_model.resources_by_type('AWS::IAM::Policy')
|
20
|
+
.select do |policy|
|
21
|
+
!policy.policy_document.allows_not_resource.empty?
|
22
|
+
end
|
21
23
|
|
22
24
|
violating_policies.map(&:logical_resource_id)
|
23
25
|
end
|
@@ -6,7 +6,8 @@ class SecurityGroupIngressOpenToWorldRule < BaseRule
|
|
6
6
|
include IpAddr
|
7
7
|
|
8
8
|
def rule_text
|
9
|
-
'Security Groups found with cidr open to world on ingress.
|
9
|
+
'Security Groups found with cidr open to world on ingress. ' \
|
10
|
+
'This should never be true on instance. Permissible on ELB'
|
10
11
|
end
|
11
12
|
|
12
13
|
def rule_type
|
@@ -18,7 +19,8 @@ class SecurityGroupIngressOpenToWorldRule < BaseRule
|
|
18
19
|
end
|
19
20
|
|
20
21
|
##
|
21
|
-
# This will behave slightly different than the legacy jq based rule
|
22
|
+
# This will behave slightly different than the legacy jq based rule
|
23
|
+
# which was targeted against inline ingress only
|
22
24
|
def audit_impl(cfn_model)
|
23
25
|
logical_resource_ids = []
|
24
26
|
cfn_model.security_groups.each do |security_group|
|
@@ -1,4 +1,7 @@
|
|
1
1
|
|
2
2
|
# failure(id: 'F8888',
|
3
|
-
# jmespath:
|
3
|
+
# jmespath:
|
4
|
+
# "Resources.*|[?Type == 'AWS::EC2::Volume' && " \
|
5
|
+
# "(Properties.Encrypted == `false` || " \
|
6
|
+
# "Properties.Encrypted == `null`)].id",
|
4
7
|
# message: 'Found a naughty EBS volume')
|
data/lib/cfn-nag/ip_addr.rb
CHANGED
@@ -2,7 +2,8 @@ require 'netaddr'
|
|
2
2
|
|
3
3
|
module IpAddr
|
4
4
|
def ip4_open?(ingress)
|
5
|
-
# only care about literals. if a Hash/Ref not going to chase it down
|
5
|
+
# only care about literals. if a Hash/Ref not going to chase it down
|
6
|
+
# given likely a Parameter with external val
|
6
7
|
ingress.cidrIp.is_a?(String) && ingress.cidrIp == '0.0.0.0/0'
|
7
8
|
end
|
8
9
|
|
@@ -10,8 +11,10 @@ module IpAddr
|
|
10
11
|
normalized_cidr_ip6 = normalize_cidr_ip6(ingress)
|
11
12
|
return false if normalized_cidr_ip6.nil?
|
12
13
|
|
13
|
-
# only care about literals. if a Hash/Ref not going to chase it down
|
14
|
-
|
14
|
+
# only care about literals. if a Hash/Ref not going to chase it down
|
15
|
+
# given likely a Parameter with external val
|
16
|
+
(NetAddr::CIDRv6.create(normalized_cidr_ip6) ==
|
17
|
+
NetAddr::CIDRv6.create('::/0'))
|
15
18
|
end
|
16
19
|
|
17
20
|
def ip4_cidr_range?(ingress)
|
@@ -22,14 +25,17 @@ module IpAddr
|
|
22
25
|
normalized_cidr_ip6 = normalize_cidr_ip6(ingress)
|
23
26
|
return false if normalized_cidr_ip6.nil?
|
24
27
|
|
25
|
-
# only care about literals. if a Hash/Ref not going to chase it down
|
28
|
+
# only care about literals. if a Hash/Ref not going to chase it down
|
29
|
+
# given likely a Parameter with external val
|
26
30
|
!NetAddr::CIDRv6.create(normalized_cidr_ip6).to_s.end_with?('/128')
|
27
31
|
end
|
28
32
|
|
29
33
|
##
|
30
34
|
# If it's a string, just pass through
|
31
|
-
# If it's a symbol - probably because the YAML.load call treats an unquoted
|
32
|
-
#
|
35
|
+
# If it's a symbol - probably because the YAML.load call treats an unquoted
|
36
|
+
# ::/0 as a the symbol :':/0'
|
37
|
+
# Otherwise it's probably a Ref or whatever and we aren't going to do
|
38
|
+
# anything with it
|
33
39
|
#
|
34
40
|
def normalize_cidr_ip6(ingress)
|
35
41
|
if ingress.cidrIpv6.is_a?(Symbol)
|
@@ -18,7 +18,8 @@ class ProfileLoader
|
|
18
18
|
if !rule_line_match.nil?
|
19
19
|
rule_id = rule_line_match.captures.first
|
20
20
|
if @rules_registry.by_id(rule_id) == nil
|
21
|
-
raise "#{rule_id} is not a legal rule identifier from:
|
21
|
+
raise "#{rule_id} is not a legal rule identifier from: " \
|
22
|
+
"#{@rules_registry.rules.map { |rule| rule.id }}"
|
22
23
|
else
|
23
24
|
new_profile.add_rule rule_id
|
24
25
|
end
|
@@ -3,7 +3,8 @@ require 'json'
|
|
3
3
|
class JsonResults
|
4
4
|
def render(results)
|
5
5
|
hashified_results = results.each do |result|
|
6
|
-
result[:file_results][:violations] =
|
6
|
+
result[:file_results][:violations] = \
|
7
|
+
result[:file_results][:violations].map(&:to_h)
|
7
8
|
end
|
8
9
|
|
9
10
|
puts JSON.pretty_generate(hashified_results)
|
data/lib/cfn-nag/rule_dumper.rb
CHANGED
@@ -15,7 +15,8 @@ class CfnNagRuleDumper
|
|
15
15
|
|
16
16
|
profile = nil
|
17
17
|
unless @profile_definition.nil?
|
18
|
-
profile = ProfileLoader.new(rule_registry)
|
18
|
+
profile = ProfileLoader.new(rule_registry)
|
19
|
+
.load(profile_definition: @profile_definition)
|
19
20
|
end
|
20
21
|
|
21
22
|
RulesView.new.emit(rule_registry, profile)
|
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.3.
|
4
|
+
version: 0.3.37
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Kascic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-03-
|
11
|
+
date: 2018-03-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|