cfn-nag 0.3.67 → 0.3.68
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 +4 -4
- data/bin/cfn_nag +14 -8
- data/bin/cfn_nag_scan +24 -9
- data/lib/cfn-nag/cfn_nag.rb +13 -13
- data/lib/cfn-nag/custom_rule_loader.rb +10 -5
- data/lib/cfn-nag/custom_rules/LambdaPermissionInvokeFunctionActionRule.rb +5 -3
- data/lib/cfn-nag/custom_rules/LambdaPermissionWildcardPrincipalRule.rb +3 -2
- data/lib/cfn-nag/custom_rules/RDSInstanceMasterUserPasswordRule.rb +10 -6
- data/lib/cfn-nag/custom_rules/RDSInstanceMasterUsernameRule.rb +2 -1
- data/lib/cfn-nag/custom_rules/RDSInstancePubliclyAccessibleRule.rb +3 -1
- data/lib/cfn-nag/custom_rules/S3BucketPublicReadWriteAclRule.rb +3 -5
- data/lib/cfn-nag/custom_rules/SecurityGroupEgressOpenToWorldRule.rb +5 -5
- data/lib/cfn-nag/custom_rules/SecurityGroupEgressPortRangeRule.rb +5 -5
- data/lib/cfn-nag/custom_rules/SecurityGroupIngressCidrNon32Rule.rb +5 -5
- data/lib/cfn-nag/custom_rules/SecurityGroupIngressOpenToWorldRule.rb +5 -5
- data/lib/cfn-nag/custom_rules/SecurityGroupIngressPortRangeRule.rb +7 -6
- data/lib/cfn-nag/custom_rules/SecurityGroupMissingEgressRule.rb +5 -5
- metadata +1 -2
- data/lib/cfn-nag/custom_rules/unencrypted_s3_put_allowed.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ffbdd25dd16e216af5280c00d56987c2236765b6e0467c3c35fc3167333802f0
|
4
|
+
data.tar.gz: 1a7d2fbeaca398861088d3f138ebd2b1bc89e03f85aa58af230353fed6790012
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 93235ea544b753874959e97ad5e72c776b40ce5687adbb3fcdf8f9f45390e9280e049f72d0974c9d8116883454f740270ec39deac382e3da20d24842e37886d4
|
7
|
+
data.tar.gz: ad62b96251d1a425007cae5cfdab6244880920489fc552136386cf7ebeed84187b5eeca21d3de19be2ff3365d553d6bb95eda268ff522e229f88ca01bf14713e
|
data/bin/cfn_nag
CHANGED
@@ -8,7 +8,12 @@ require 'json'
|
|
8
8
|
require 'rubygems/specification'
|
9
9
|
|
10
10
|
opts = Trollop.options do
|
11
|
-
|
11
|
+
options_message = '[options] <cloudformation template path ...>|' \
|
12
|
+
'<cloudformation template in STDIN>'
|
13
|
+
custom_rule_exceptions_message = 'Isolate custom rule exceptions - ' \
|
14
|
+
'just emit the exception without stack ' \
|
15
|
+
' trace and keep chugging'
|
16
|
+
usage options_message
|
12
17
|
version Gem::Specification.find_by_name('cfn-nag').version
|
13
18
|
|
14
19
|
opt :debug,
|
@@ -42,7 +47,7 @@ opts = Trollop.options do
|
|
42
47
|
required: false,
|
43
48
|
default: nil
|
44
49
|
opt :isolate_custom_rule_exceptions,
|
45
|
-
|
50
|
+
custom_rule_exceptions_message,
|
46
51
|
type: :boolean,
|
47
52
|
required: false,
|
48
53
|
default: false
|
@@ -60,12 +65,13 @@ unless opts[:parameter_values_path].nil?
|
|
60
65
|
parameter_values_string = IO.read(opts[:parameter_values_path])
|
61
66
|
end
|
62
67
|
|
63
|
-
cfn_nag = CfnNag.new(
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
68
|
+
cfn_nag = CfnNag.new(
|
69
|
+
profile_definition: profile_definition,
|
70
|
+
rule_directory: opts[:rule_directory],
|
71
|
+
allow_suppression: opts[:allow_suppression],
|
72
|
+
print_suppression: opts[:print_suppression],
|
73
|
+
isolate_custom_rule_exceptions: opts[:isolate_custom_rule_exceptions]
|
74
|
+
)
|
69
75
|
|
70
76
|
total_failure_count = 0
|
71
77
|
until ARGF.closed? || ARGF.eof?
|
data/bin/cfn_nag_scan
CHANGED
@@ -10,8 +10,20 @@ require 'rubygems/specification'
|
|
10
10
|
opts = Trollop.options do
|
11
11
|
version Gem::Specification.find_by_name('cfn-nag').version
|
12
12
|
|
13
|
+
input_path_message = 'CloudFormation template to nag on or directory of ' \
|
14
|
+
'templates. Default is all *.json, *.yaml, *.yml ' \
|
15
|
+
'and *.template recursively, but can be constrained ' \
|
16
|
+
'by --template-pattern'
|
17
|
+
|
18
|
+
custom_rule_exceptions_message = 'Isolate custom rule exceptions - just ' \
|
19
|
+
'emit the exception without stack trace ' \
|
20
|
+
'and keep chugging'
|
21
|
+
|
22
|
+
template_pattern_message = 'Within the --input-path, match files to scan ' \
|
23
|
+
'against this regular expression'
|
24
|
+
|
13
25
|
opt :input_path,
|
14
|
-
|
26
|
+
input_path_message,
|
15
27
|
type: :io,
|
16
28
|
required: true
|
17
29
|
opt :output_format,
|
@@ -49,12 +61,12 @@ opts = Trollop.options do
|
|
49
61
|
required: false,
|
50
62
|
default: false
|
51
63
|
opt :isolate_custom_rule_exceptions,
|
52
|
-
|
64
|
+
custom_rule_exceptions_message,
|
53
65
|
type: :boolean,
|
54
66
|
required: false,
|
55
67
|
default: false
|
56
68
|
opt :template_pattern,
|
57
|
-
|
69
|
+
template_pattern_message,
|
58
70
|
type: :string,
|
59
71
|
required: false,
|
60
72
|
default: '..*\.json|..*\.yaml|..*\.yml|..*\.template'
|
@@ -72,14 +84,17 @@ unless opts[:profile_path].nil?
|
|
72
84
|
profile_definition = IO.read(opts[:profile_path])
|
73
85
|
end
|
74
86
|
|
75
|
-
cfn_nag = CfnNag.new(
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
87
|
+
cfn_nag = CfnNag.new(
|
88
|
+
profile_definition: profile_definition,
|
89
|
+
rule_directory: opts[:rule_directory],
|
90
|
+
allow_suppression: opts[:allow_suppression],
|
91
|
+
print_suppression: opts[:print_suppression],
|
92
|
+
isolate_custom_rule_exceptions: opts[:isolate_custom_rule_exceptions]
|
93
|
+
)
|
80
94
|
|
81
95
|
exit cfn_nag.audit_aggregate_across_files_and_render_results(
|
82
|
-
input_path: opts[:input_path],
|
96
|
+
input_path: opts[:input_path],
|
97
|
+
output_format: opts[:output_format],
|
83
98
|
parameter_values_path: opts[:parameter_values_path],
|
84
99
|
template_pattern: opts[:template_pattern]
|
85
100
|
)
|
data/lib/cfn-nag/cfn_nag.rb
CHANGED
@@ -66,19 +66,6 @@ class CfnNag
|
|
66
66
|
aggregate_results
|
67
67
|
end
|
68
68
|
|
69
|
-
def audit_result(violations)
|
70
|
-
{
|
71
|
-
failure_count: Violation.count_failures(violations),
|
72
|
-
violations: violations
|
73
|
-
}
|
74
|
-
end
|
75
|
-
|
76
|
-
def fatal_violation(message)
|
77
|
-
Violation.new(id: 'FATAL',
|
78
|
-
type: Violation::FAILING_VIOLATION,
|
79
|
-
message: message)
|
80
|
-
end
|
81
|
-
|
82
69
|
##
|
83
70
|
# Given cloudformation json/yml, run all the rules against it
|
84
71
|
#
|
@@ -118,6 +105,19 @@ class CfnNag
|
|
118
105
|
|
119
106
|
private
|
120
107
|
|
108
|
+
def audit_result(violations)
|
109
|
+
{
|
110
|
+
failure_count: Violation.count_failures(violations),
|
111
|
+
violations: violations
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def fatal_violation(message)
|
116
|
+
Violation.new(id: 'FATAL',
|
117
|
+
type: Violation::FAILING_VIOLATION,
|
118
|
+
message: message)
|
119
|
+
end
|
120
|
+
|
121
121
|
def filter_violations_by_profile(violations)
|
122
122
|
profile = nil
|
123
123
|
unless @profile_definition.nil?
|
@@ -78,9 +78,11 @@ class CustomRuleLoader
|
|
78
78
|
def filter_rule_classes(cfn_model, violations)
|
79
79
|
discover_rule_classes(@rule_directory).each do |rule_class|
|
80
80
|
begin
|
81
|
-
filtered_cfn_model = cfn_model_with_suppressed_resources_removed
|
82
|
-
|
83
|
-
|
81
|
+
filtered_cfn_model = cfn_model_with_suppressed_resources_removed(
|
82
|
+
cfn_model: cfn_model,
|
83
|
+
rule_id: rule_class.new.rule_id,
|
84
|
+
allow_suppression: @allow_suppression
|
85
|
+
)
|
84
86
|
audit_result = rule_class.new.audit(filtered_cfn_model)
|
85
87
|
violations << audit_result unless audit_result.nil?
|
86
88
|
rescue Exception => exception
|
@@ -122,7 +124,8 @@ class CustomRuleLoader
|
|
122
124
|
logical_resource_id = mangled_metadata.first
|
123
125
|
mangled_rules = mangled_metadata[1]
|
124
126
|
|
125
|
-
STDERR.puts "#{logical_resource_id} has missing cfn_nag suppression
|
127
|
+
STDERR.puts "#{logical_resource_id} has missing cfn_nag suppression " \
|
128
|
+
"rule id: #{mangled_rules}"
|
126
129
|
end
|
127
130
|
end
|
128
131
|
|
@@ -132,7 +135,9 @@ class CustomRuleLoader
|
|
132
135
|
rule_to_suppress['id'] == rule_id
|
133
136
|
end
|
134
137
|
if found_suppression_rule && @print_suppression
|
135
|
-
|
138
|
+
message = "Suppressing #{rule_id} on #{logical_resource_id} for " \
|
139
|
+
"reason: #{found_suppression_rule['reason']}"
|
140
|
+
STDERR.puts message
|
136
141
|
end
|
137
142
|
!found_suppression_rule.nil?
|
138
143
|
end
|
@@ -5,7 +5,8 @@ require_relative 'base'
|
|
5
5
|
|
6
6
|
class LambdaPermissionInvokeFunctionActionRule < BaseRule
|
7
7
|
def rule_text
|
8
|
-
'Lambda permission beside InvokeFunction might not be what you want?
|
8
|
+
'Lambda permission beside InvokeFunction might not be what you want? ' \
|
9
|
+
'Not sure!?'
|
9
10
|
end
|
10
11
|
|
11
12
|
def rule_type
|
@@ -17,10 +18,11 @@ class LambdaPermissionInvokeFunctionActionRule < BaseRule
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def audit_impl(cfn_model)
|
20
|
-
|
21
|
+
lambda_permissions = cfn_model.resources_by_type('AWS::Lambda::Permission')
|
22
|
+
violating_lambda_permissions = lambda_permissions.select do |lambda_permission|
|
21
23
|
lambda_permission.action != 'lambda:InvokeFunction'
|
22
24
|
end
|
23
25
|
|
24
|
-
|
26
|
+
violating_lambda_permissions.map(&:logical_resource_id)
|
25
27
|
end
|
26
28
|
end
|
@@ -18,10 +18,11 @@ class LambdaPermissionWildcardPrincipalRule < BaseRule
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def audit_impl(cfn_model)
|
21
|
-
|
21
|
+
lambda_permissions = cfn_model.resources_by_type('AWS::Lambda::Permission')
|
22
|
+
violating_lambda_permissions = lambda_permissions.select do |lambda_permission|
|
22
23
|
LambdaPrincipal.wildcard? lambda_permission.principal
|
23
24
|
end
|
24
25
|
|
25
|
-
|
26
|
+
violating_lambda_permissions.map(&:logical_resource_id)
|
26
27
|
end
|
27
28
|
end
|
@@ -5,7 +5,8 @@ require_relative 'base'
|
|
5
5
|
|
6
6
|
class RDSInstanceMasterUserPasswordRule < BaseRule
|
7
7
|
def rule_text
|
8
|
-
'RDS instance master user password must be Ref to NoEcho Parameter.
|
8
|
+
'RDS instance master user password must be Ref to NoEcho Parameter. ' \
|
9
|
+
'Default credentials are not recommended'
|
9
10
|
end
|
10
11
|
|
11
12
|
def rule_type
|
@@ -16,15 +17,18 @@ class RDSInstanceMasterUserPasswordRule < BaseRule
|
|
16
17
|
'F23'
|
17
18
|
end
|
18
19
|
|
19
|
-
# one word of warning... if somebody applies parameter values via JSON....
|
20
|
-
#
|
20
|
+
# one word of warning... if somebody applies parameter values via JSON....
|
21
|
+
# this will compare that....
|
22
|
+
# probably shouldn't be doing that though if it's NoEcho there's a good reason
|
21
23
|
# bother checking synthesized_value? that would be the indicator.....
|
22
24
|
def audit_impl(cfn_model)
|
23
|
-
|
25
|
+
rds_dbinstances = cfn_model.resources_by_type('AWS::RDS::DBInstance')
|
26
|
+
violating_rdsinstances = rds_dbinstances.select do |instance|
|
24
27
|
if instance.masterUserPassword.nil?
|
25
28
|
false
|
26
29
|
else
|
27
|
-
!
|
30
|
+
!no_echo_parameter_without_default?(cfn_model,
|
31
|
+
instance.masterUserPassword)
|
28
32
|
end
|
29
33
|
end
|
30
34
|
|
@@ -37,7 +41,7 @@ class RDSInstanceMasterUserPasswordRule < BaseRule
|
|
37
41
|
string.to_s.casecmp('true').zero?
|
38
42
|
end
|
39
43
|
|
40
|
-
def
|
44
|
+
def no_echo_parameter_without_default?(cfn_model, master_user_password)
|
41
45
|
# i feel like i've written this mess somewhere before
|
42
46
|
if master_user_password.is_a? Hash
|
43
47
|
if master_user_password.key? 'Ref'
|
@@ -6,7 +6,8 @@ require_relative 'base'
|
|
6
6
|
# cfn_nag rules related to RDS Instance master username
|
7
7
|
class RDSInstanceMasterUsernameRule < BaseRule
|
8
8
|
def rule_text
|
9
|
-
'RDS instance master username must be Ref to NoEcho Parameter. Default
|
9
|
+
'RDS instance master username must be Ref to NoEcho Parameter. Default ' \
|
10
|
+
'credentials are not recommended'
|
10
11
|
end
|
11
12
|
|
12
13
|
def rule_type
|
@@ -17,7 +17,9 @@ class RDSInstancePubliclyAccessibleRule < BaseRule
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def audit_impl(cfn_model)
|
20
|
-
|
20
|
+
rds_dbinstances = cfn_model.resources_by_type('AWS::RDS::DBInstance')
|
21
|
+
|
22
|
+
violating_rdsinstances = rds_dbinstances.select do |instance|
|
21
23
|
instance.publiclyAccessible.nil? || instance.publiclyAccessible.to_s.casecmp('true').zero?
|
22
24
|
end
|
23
25
|
|
@@ -17,12 +17,10 @@ class S3BucketPublicReadWriteAclRule < BaseRule
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def audit_impl(cfn_model)
|
20
|
-
|
21
|
-
|
22
|
-
cfn_model.resources_by_type('AWS::S3::Bucket').each do |bucket|
|
23
|
-
logical_resource_ids << bucket.logical_resource_id if bucket.accessControl == 'PublicReadWrite'
|
20
|
+
violating_buckets = cfn_model.resources_by_type('AWS::S3::Bucket').select do |bucket|
|
21
|
+
bucket.accessControl == 'PublicReadWrite'
|
24
22
|
end
|
25
23
|
|
26
|
-
|
24
|
+
violating_buckets.map(&:logical_resource_id)
|
27
25
|
end
|
28
26
|
end
|
@@ -20,21 +20,21 @@ class SecurityGroupEgressOpenToWorldRule < BaseRule
|
|
20
20
|
end
|
21
21
|
|
22
22
|
##
|
23
|
-
# This will behave slightly different than the legacy jq based rule which was
|
23
|
+
# This will behave slightly different than the legacy jq based rule which was
|
24
|
+
# targeted against inline ingress only
|
24
25
|
def audit_impl(cfn_model)
|
25
|
-
|
26
|
-
cfn_model.security_groups.each do |security_group|
|
26
|
+
violating_security_groups = cfn_model.security_groups.select do |security_group|
|
27
27
|
violating_egresses = security_group.egresses.select do |egress|
|
28
28
|
ip4_open?(egress) || ip6_open?(egress)
|
29
29
|
end
|
30
30
|
|
31
|
-
|
31
|
+
!violating_egresses.empty?
|
32
32
|
end
|
33
33
|
|
34
34
|
violating_egresses = cfn_model.standalone_egress.select do |standalone_egress|
|
35
35
|
ip4_open?(standalone_egress) || ip6_open?(standalone_egress)
|
36
36
|
end
|
37
37
|
|
38
|
-
|
38
|
+
violating_security_groups.map(&:logical_resource_id) + violating_egresses.map(&:logical_resource_id)
|
39
39
|
end
|
40
40
|
end
|
@@ -17,21 +17,21 @@ class SecurityGroupEgressPortRangeRule < BaseRule
|
|
17
17
|
end
|
18
18
|
|
19
19
|
##
|
20
|
-
# This will behave slightly different than the legacy jq based rule which was
|
20
|
+
# This will behave slightly different than the legacy jq based rule which was
|
21
|
+
# targeted against inline ingress only
|
21
22
|
def audit_impl(cfn_model)
|
22
|
-
|
23
|
-
cfn_model.security_groups.each do |security_group|
|
23
|
+
violating_security_groups = cfn_model.security_groups.select do |security_group|
|
24
24
|
violating_egresses = security_group.egresses.select do |egress|
|
25
25
|
egress.fromPort != egress.toPort
|
26
26
|
end
|
27
27
|
|
28
|
-
|
28
|
+
!violating_egresses.empty?
|
29
29
|
end
|
30
30
|
|
31
31
|
violating_egresses = cfn_model.standalone_egress.select do |standalone_egress|
|
32
32
|
standalone_egress.fromPort != standalone_egress.toPort
|
33
33
|
end
|
34
34
|
|
35
|
-
|
35
|
+
violating_security_groups.map(&:logical_resource_id) + violating_egresses.map(&:logical_resource_id)
|
36
36
|
end
|
37
37
|
end
|
@@ -20,21 +20,21 @@ class SecurityGroupIngressCidrNon32Rule < BaseRule
|
|
20
20
|
end
|
21
21
|
|
22
22
|
##
|
23
|
-
# This will behave slightly different than the legacy jq based rule which was
|
23
|
+
# This will behave slightly different than the legacy jq based rule which was
|
24
|
+
# targeted against inline ingress only
|
24
25
|
def audit_impl(cfn_model)
|
25
|
-
|
26
|
-
cfn_model.security_groups.each do |security_group|
|
26
|
+
violating_security_groups = cfn_model.security_groups.select do |security_group|
|
27
27
|
violating_ingresses = security_group.ingresses.select do |ingress|
|
28
28
|
ip4_cidr_range?(ingress) || ip6_cidr_range?(ingress)
|
29
29
|
end
|
30
30
|
|
31
|
-
|
31
|
+
!violating_ingresses.empty?
|
32
32
|
end
|
33
33
|
|
34
34
|
violating_ingresses = cfn_model.standalone_ingress.select do |standalone_ingress|
|
35
35
|
ip4_cidr_range?(standalone_ingress) || ip6_cidr_range?(standalone_ingress)
|
36
36
|
end
|
37
37
|
|
38
|
-
|
38
|
+
violating_security_groups.map(&:logical_resource_id) + violating_ingresses.map(&:logical_resource_id)
|
39
39
|
end
|
40
40
|
end
|
@@ -8,7 +8,8 @@ class SecurityGroupIngressOpenToWorldRule < BaseRule
|
|
8
8
|
include IpAddr
|
9
9
|
|
10
10
|
def rule_text
|
11
|
-
'Security Groups found with cidr open to world on ingress. This should
|
11
|
+
'Security Groups found with cidr open to world on ingress. This should ' \
|
12
|
+
'never be true on instance. Permissible on ELB'
|
12
13
|
end
|
13
14
|
|
14
15
|
def rule_type
|
@@ -23,19 +24,18 @@ class SecurityGroupIngressOpenToWorldRule < BaseRule
|
|
23
24
|
# This will behave slightly different than the legacy jq based rule
|
24
25
|
# which was targeted against inline ingress only
|
25
26
|
def audit_impl(cfn_model)
|
26
|
-
|
27
|
-
cfn_model.security_groups.each do |security_group|
|
27
|
+
violating_security_groups = cfn_model.security_groups.select do |security_group|
|
28
28
|
violating_ingresses = security_group.ingresses.select do |ingress|
|
29
29
|
ip4_open?(ingress) || ip6_open?(ingress)
|
30
30
|
end
|
31
31
|
|
32
|
-
|
32
|
+
!violating_ingresses.empty?
|
33
33
|
end
|
34
34
|
|
35
35
|
violating_ingresses = cfn_model.standalone_ingress.select do |standalone_ingress|
|
36
36
|
ip4_open?(standalone_ingress) || ip6_open?(standalone_ingress)
|
37
37
|
end
|
38
38
|
|
39
|
-
|
39
|
+
violating_security_groups.map(&:logical_resource_id) + violating_ingresses.map(&:logical_resource_id)
|
40
40
|
end
|
41
41
|
end
|
@@ -5,7 +5,8 @@ require_relative 'base'
|
|
5
5
|
|
6
6
|
class SecurityGroupIngressPortRangeRule < BaseRule
|
7
7
|
def rule_text
|
8
|
-
'Security Groups found ingress with port range instead of just a single
|
8
|
+
'Security Groups found ingress with port range instead of just a single ' \
|
9
|
+
'port'
|
9
10
|
end
|
10
11
|
|
11
12
|
def rule_type
|
@@ -17,21 +18,21 @@ class SecurityGroupIngressPortRangeRule < BaseRule
|
|
17
18
|
end
|
18
19
|
|
19
20
|
##
|
20
|
-
# This will behave slightly different than the legacy jq based rule which was
|
21
|
+
# This will behave slightly different than the legacy jq based rule which was
|
22
|
+
# targeted against inline ingress only
|
21
23
|
def audit_impl(cfn_model)
|
22
|
-
|
23
|
-
cfn_model.security_groups.each do |security_group|
|
24
|
+
violating_security_groups = cfn_model.security_groups.select do |security_group|
|
24
25
|
violating_ingresses = security_group.ingresses.select do |ingress|
|
25
26
|
ingress.fromPort != ingress.toPort
|
26
27
|
end
|
27
28
|
|
28
|
-
|
29
|
+
!violating_ingresses.empty?
|
29
30
|
end
|
30
31
|
|
31
32
|
violating_ingresses = cfn_model.standalone_ingress.select do |standalone_ingress|
|
32
33
|
standalone_ingress.fromPort != standalone_ingress.toPort
|
33
34
|
end
|
34
35
|
|
35
|
-
|
36
|
+
violating_security_groups.map(&:logical_resource_id) + violating_ingresses.map(&:logical_resource_id)
|
36
37
|
end
|
37
38
|
end
|
@@ -5,7 +5,8 @@ require_relative 'base'
|
|
5
5
|
|
6
6
|
class SecurityGroupMissingEgressRule < BaseRule
|
7
7
|
def rule_text
|
8
|
-
'Missing egress rule means all traffic is allowed outbound. Make this
|
8
|
+
'Missing egress rule means all traffic is allowed outbound. Make this ' \
|
9
|
+
'explicit if it is desired configuration'
|
9
10
|
end
|
10
11
|
|
11
12
|
def rule_type
|
@@ -17,11 +18,10 @@ class SecurityGroupMissingEgressRule < BaseRule
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def audit_impl(cfn_model)
|
20
|
-
|
21
|
-
|
22
|
-
logical_resource_ids << security_group.logical_resource_id if security_group.egresses.empty?
|
21
|
+
violating_security_groups = cfn_model.security_groups.select do |security_group|
|
22
|
+
security_group.egresses.empty?
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
violating_security_groups.map(&:logical_resource_id)
|
26
26
|
end
|
27
27
|
end
|
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.3.
|
4
|
+
version: 0.3.68
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Kascic
|
@@ -193,7 +193,6 @@ files:
|
|
193
193
|
- lib/cfn-nag/custom_rules/WafWebAclDefaultActionRule.rb
|
194
194
|
- lib/cfn-nag/custom_rules/WorkspacesWorkspaceEncryptionRule.rb
|
195
195
|
- lib/cfn-nag/custom_rules/base.rb
|
196
|
-
- lib/cfn-nag/custom_rules/unencrypted_s3_put_allowed.rb
|
197
196
|
- lib/cfn-nag/ip_addr.rb
|
198
197
|
- lib/cfn-nag/jmes_path_discovery.rb
|
199
198
|
- lib/cfn-nag/jmes_path_evaluator.rb
|
@@ -1,60 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# require 'cfn-nag/violation'
|
4
|
-
# require 'model/action_parser'
|
5
|
-
#
|
6
|
-
# class UnencryptedS3PutObjectAllowedRule
|
7
|
-
#
|
8
|
-
# def rule_text
|
9
|
-
# 'It appears that the S3 Bucket Policy allows s3:PutObject without server-side encryption'
|
10
|
-
# end
|
11
|
-
#
|
12
|
-
# def rule_type
|
13
|
-
# Violation::WARNING
|
14
|
-
# end
|
15
|
-
#
|
16
|
-
# def rule_id
|
17
|
-
# 'W1000'
|
18
|
-
# end
|
19
|
-
#
|
20
|
-
# def audit(cfn_model)
|
21
|
-
# logical_resource_ids = []
|
22
|
-
# cfn_model.bucket_policies.each do |bucket_policy|
|
23
|
-
# found_statement = bucket_policy.statements.find do |statement|
|
24
|
-
# blocks_put_object_without_encryption(statement)
|
25
|
-
# end
|
26
|
-
# if found_statement.nil?
|
27
|
-
# logical_resource_ids << bucket_policy.logical_resource_id
|
28
|
-
# end
|
29
|
-
# end
|
30
|
-
#
|
31
|
-
# if logical_resource_ids.size > 0
|
32
|
-
# Violation.new(id: rule_id,
|
33
|
-
# type: rule_type,
|
34
|
-
# message: rule_text,
|
35
|
-
# logical_resource_ids: logical_resource_ids)
|
36
|
-
# else
|
37
|
-
# nil
|
38
|
-
# end
|
39
|
-
# end
|
40
|
-
#
|
41
|
-
# private
|
42
|
-
#
|
43
|
-
# def blocks_put_object_without_encryption(statement)
|
44
|
-
# encryption_condition = {
|
45
|
-
# 'StringNotEquals' => {
|
46
|
-
# 's3:x-amz-server-side-encryption' => 'AES256'
|
47
|
-
# }
|
48
|
-
# }
|
49
|
-
#
|
50
|
-
# # this isn't quite complete. parsing the Resource field can be tricky
|
51
|
-
# # looking for a trailing wildcard will likely be right most of the time
|
52
|
-
# # but there are a lot of string manipulations to confuse things so...
|
53
|
-
# # just warn when we can't find at least the Deny+encryption - they may have an
|
54
|
-
# # incomplete Deny (for encryption) and that will slip through
|
55
|
-
# statement['Effect'] == 'Deny' and
|
56
|
-
# ActionParser.new.include?(actual_action: statement['Action'], action_to_look_for: 's3:PutObject') and
|
57
|
-
# S3BucketPolicy::condition_includes?(statement, encryption_condition) and
|
58
|
-
# statement['Principal'] == '*'
|
59
|
-
# end
|
60
|
-
# end
|