cfn-nag 0.0.17 → 0.0.18
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/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
|