cfn-nag 0.0.17 → 0.0.18
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.rb +3 -1
- data/lib/custom_rules/unencrypted_s3_put_allowed.rb +49 -0
- data/lib/model/action_parser.rb +27 -0
- data/lib/model/cfn_model.rb +8 -9
- data/lib/model/s3_bucket_policy.rb +25 -0
- data/lib/model/s3_bucket_policy_parser.rb +28 -0
- metadata +5 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 167b9ddba20a5fa469d15c3aaf0a3759503e8ee9
|
4
|
+
data.tar.gz: ffba250d7d7929c7cfbcad43ad0573f99d16b8f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a09226380b2020d11fb41e07a758095a7f05f89d87c62d734ff8e7bb5e76cbc36c9ddd565af84b271698c56d60a5fe42f0145618d4bc87d8eeec7384445d1df
|
7
|
+
data.tar.gz: 738ccc891e94e19f4f8bfa50bbdeda2621319a13c3ad77d3939a6087f98d10bee517ec52cd4be1bd6bb5a03158e87813feb0bbe31db81e77ff81a90b16ae221b
|
data/lib/cfn_nag.rb
CHANGED
@@ -4,6 +4,7 @@ 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_relative 'custom_rules/unencrypted_s3_put_allowed'
|
7
8
|
require 'tempfile'
|
8
9
|
|
9
10
|
class CfnNag
|
@@ -175,7 +176,8 @@ class CfnNag
|
|
175
176
|
def custom_rule_registry
|
176
177
|
[
|
177
178
|
SecurityGroupMissingEgressRule,
|
178
|
-
UserMissingGroupRule
|
179
|
+
UserMissingGroupRule,
|
180
|
+
UnencryptedS3PutObjectAllowedRule
|
179
181
|
]
|
180
182
|
end
|
181
183
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative '../violation'
|
2
|
+
require 'model/action_parser'
|
3
|
+
|
4
|
+
class UnencryptedS3PutObjectAllowedRule
|
5
|
+
|
6
|
+
def rule_text
|
7
|
+
'It appears that the S3 Bucket Policy allows s3:PutObject without server-side encryption'
|
8
|
+
end
|
9
|
+
|
10
|
+
def audit(cfn_model)
|
11
|
+
logical_resource_ids = []
|
12
|
+
cfn_model.bucket_policies.each do |bucket_policy|
|
13
|
+
found_statement = bucket_policy.statements.find do |statement|
|
14
|
+
blocks_put_object_without_encryption(statement)
|
15
|
+
end
|
16
|
+
if found_statement.nil?
|
17
|
+
logical_resource_ids << bucket_policy.logical_resource_id
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if logical_resource_ids.size > 0
|
22
|
+
Violation.new(type: Violation::WARNING,
|
23
|
+
message: rule_text,
|
24
|
+
logical_resource_ids: logical_resource_ids)
|
25
|
+
else
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def blocks_put_object_without_encryption(statement)
|
33
|
+
encryption_condition = {
|
34
|
+
'StringNotEquals' => {
|
35
|
+
's3:x-amz-server-side-encryption' => 'AES256'
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
# this isn't quite complete. parsing the Resource field can be tricky
|
40
|
+
# looking for a trailing wildcard will likely be right most of the time
|
41
|
+
# but there are a lot of string manipulations to confuse things so...
|
42
|
+
# just warn when we can't find at least the Deny+encryption - they may have an
|
43
|
+
# incomplete Deny (for encryption) and that will slip through
|
44
|
+
statement['Effect'] == 'Deny' and
|
45
|
+
ActionParser.new.include?(actual_action: statement['Action'], action_to_look_for: 's3:PutObject') and
|
46
|
+
S3BucketPolicy::condition_includes?(statement, encryption_condition) and
|
47
|
+
statement['Principal'] == '*'
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class ActionParser
|
4
|
+
|
5
|
+
def include?(actual_action:, action_to_look_for:)
|
6
|
+
if actual_action.is_a? Array
|
7
|
+
matches = actual_action.find do |single_action|
|
8
|
+
match_potentially_wildcard_action(actual_action: single_action,
|
9
|
+
action_to_look_for: action_to_look_for)
|
10
|
+
end
|
11
|
+
not matches.nil?
|
12
|
+
elsif actual_action.is_a? String
|
13
|
+
match_potentially_wildcard_action(actual_action: actual_action,
|
14
|
+
action_to_look_for: action_to_look_for)
|
15
|
+
else
|
16
|
+
fail "actual_action needs to be a String or Array (of String),not : #{actual_action.class}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def match_potentially_wildcard_action(actual_action:, action_to_look_for:)
|
23
|
+
actual_action_regex = actual_action.gsub /\*/, '.*'
|
24
|
+
match_position = Regexp.new(actual_action_regex) =~ action_to_look_for
|
25
|
+
not match_position.nil?
|
26
|
+
end
|
27
|
+
end
|
data/lib/model/cfn_model.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'json'
|
2
2
|
require_relative 'security_group_parser'
|
3
3
|
require_relative 'iam_user_parser'
|
4
|
+
require_relative 's3_bucket_policy_parser'
|
4
5
|
|
5
6
|
# consider a canonical form for template too...
|
6
7
|
# always transform optional things into more general forms....
|
@@ -13,7 +14,8 @@ class CfnModel
|
|
13
14
|
'AWS::EC2::SecurityGroupIngress' => SecurityGroupXgressParser,
|
14
15
|
'AWS::EC2::SecurityGroupEgress' => SecurityGroupXgressParser,
|
15
16
|
'AWS::IAM::User' => IamUserParser,
|
16
|
-
'AWS::IAM::UserToGroupAddition' => IamUserToGroupAdditionParser
|
17
|
+
'AWS::IAM::UserToGroupAddition' => IamUserToGroupAdditionParser,
|
18
|
+
'AWS::S3::BucketPolicy' => S3BucketPolicyParser
|
17
19
|
}
|
18
20
|
@dangling_ingress_or_egress_rules = []
|
19
21
|
@dangler = Object.new
|
@@ -44,15 +46,12 @@ class CfnModel
|
|
44
46
|
iam_users_hash.values
|
45
47
|
end
|
46
48
|
|
49
|
+
def bucket_policies
|
50
|
+
bucket_policy_hash = resources_by_type('AWS::S3::BucketPolicy')
|
51
|
+
bucket_policy_hash.values
|
52
|
+
end
|
53
|
+
|
47
54
|
private
|
48
|
-
#
|
49
|
-
# def is_get_att(object)
|
50
|
-
# not object['Fn::GetAtt'].nil?
|
51
|
-
# end
|
52
|
-
#
|
53
|
-
# def is_reference(object)
|
54
|
-
# not object['Ref'].nil?
|
55
|
-
# end
|
56
55
|
|
57
56
|
def resolve_user_logical_resource_id(user)
|
58
57
|
if not user['Ref'].nil?
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class S3BucketPolicy
|
2
|
+
attr_accessor :logical_resource_id
|
3
|
+
|
4
|
+
attr_reader :statements
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@statements = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_statement(statement_hash)
|
11
|
+
statements << statement_hash
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.condition_includes?(statement, condition_hash)
|
15
|
+
if statement['Condition'].nil?
|
16
|
+
false
|
17
|
+
else
|
18
|
+
if statement['Condition'].is_a? Hash
|
19
|
+
statement['Condition'] == condition_hash
|
20
|
+
else
|
21
|
+
statement['Condition'].include? condition_hash
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 's3_bucket_policy'
|
2
|
+
|
3
|
+
|
4
|
+
class S3BucketPolicyParser
|
5
|
+
|
6
|
+
def parse(resource_name, resource_json)
|
7
|
+
properties = resource_json['Properties']
|
8
|
+
bucket_policy = S3BucketPolicy.new
|
9
|
+
|
10
|
+
bucket_policy.logical_resource_id = resource_name
|
11
|
+
|
12
|
+
unless properties.nil?
|
13
|
+
policy_document = properties['PolicyDocument']
|
14
|
+
unless policy_document.nil?
|
15
|
+
unless policy_document['Statement'].nil?
|
16
|
+
if policy_document['Statement'].is_a? Array
|
17
|
+
policy_document['Statement'].each { |statement | bucket_policy.add_statement(statement)}
|
18
|
+
else
|
19
|
+
bucket_policy.add_statement(policy_document['Statement'])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
bucket_policy
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
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.
|
4
|
+
version: 0.0.18
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- someguy
|
@@ -50,6 +50,7 @@ files:
|
|
50
50
|
- bin/cfn_nag_rules
|
51
51
|
- lib/cfn_nag.rb
|
52
52
|
- lib/custom_rules/security_group_missing_egress.rb
|
53
|
+
- lib/custom_rules/unencrypted_s3_put_allowed.rb
|
53
54
|
- lib/custom_rules/user_missing_group.rb
|
54
55
|
- lib/json_rules/basic_rules.rb
|
55
56
|
- lib/json_rules/cfn_rules.rb
|
@@ -64,8 +65,11 @@ files:
|
|
64
65
|
- lib/json_rules/s3_bucket_rules.rb
|
65
66
|
- lib/json_rules/sns_rules.rb
|
66
67
|
- lib/json_rules/sqs_rules.rb
|
68
|
+
- lib/model/action_parser.rb
|
67
69
|
- lib/model/cfn_model.rb
|
68
70
|
- lib/model/iam_user_parser.rb
|
71
|
+
- lib/model/s3_bucket_policy.rb
|
72
|
+
- lib/model/s3_bucket_policy_parser.rb
|
69
73
|
- lib/model/security_group_parser.rb
|
70
74
|
- lib/result_view/json_results.rb
|
71
75
|
- lib/result_view/simple_stdout_results.rb
|