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 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