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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d3284f4eba9d2cab9d7790dab39273f9401fbb09
4
- data.tar.gz: e715f4800f39198bd95bc1fb6044ce1dd64644d6
3
+ metadata.gz: 167b9ddba20a5fa469d15c3aaf0a3759503e8ee9
4
+ data.tar.gz: ffba250d7d7929c7cfbcad43ad0573f99d16b8f9
5
5
  SHA512:
6
- metadata.gz: 06b61112e6e6cff426fcd6e07af0c0aa75f85f38914816bc1df48286e4aec04d76d9000d61202c9fae3a138e50d55c9af8ca0da56971f07644bdfde4b80f92a8
7
- data.tar.gz: 43147a231964233735ee827ee0f1c9ec4894f6725904e95b137b3e8205181213d5ea5698e75ce704fd695025015e9a31467d65d1e14d0580587d79dfbdc75071
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
@@ -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.17
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