cfn-nag 0.3.67 → 0.3.68
Sign up to get free protection for your applications and to get access to all the features.
- 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
|