inspec 4.22.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +63 -0
  3. data/inspec.gemspec +36 -0
  4. data/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/Gemfile +11 -0
  5. data/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/inspec-plugin-template.gemspec +43 -0
  6. data/lib/plugins/inspec-init/templates/profiles/aws/README.md +192 -0
  7. data/lib/plugins/inspec-init/templates/profiles/aws/attributes.yml +2 -0
  8. data/lib/plugins/inspec-init/templates/profiles/aws/controls/example.rb +39 -0
  9. data/lib/plugins/inspec-init/templates/profiles/aws/inspec.yml +22 -0
  10. data/lib/plugins/inspec-init/templates/profiles/azure/README.md +56 -0
  11. data/lib/plugins/inspec-init/templates/profiles/azure/controls/example.rb +14 -0
  12. data/lib/plugins/inspec-init/templates/profiles/azure/inspec.yml +14 -0
  13. data/lib/plugins/inspec-init/templates/profiles/gcp/README.md +66 -0
  14. data/lib/plugins/inspec-init/templates/profiles/gcp/attributes.yml +2 -0
  15. data/lib/plugins/inspec-init/templates/profiles/gcp/controls/example.rb +27 -0
  16. data/lib/plugins/inspec-init/templates/profiles/gcp/inspec.yml +19 -0
  17. data/lib/resource_support/aws.rb +76 -0
  18. data/lib/resource_support/aws/aws_backend_base.rb +12 -0
  19. data/lib/resource_support/aws/aws_backend_factory_mixin.rb +12 -0
  20. data/lib/resource_support/aws/aws_plural_resource_mixin.rb +24 -0
  21. data/lib/resource_support/aws/aws_resource_mixin.rb +69 -0
  22. data/lib/resource_support/aws/aws_singular_resource_mixin.rb +27 -0
  23. data/lib/resources/aws/aws_billing_report.rb +107 -0
  24. data/lib/resources/aws/aws_billing_reports.rb +74 -0
  25. data/lib/resources/aws/aws_cloudtrail_trail.rb +97 -0
  26. data/lib/resources/aws/aws_cloudtrail_trails.rb +51 -0
  27. data/lib/resources/aws/aws_cloudwatch_alarm.rb +67 -0
  28. data/lib/resources/aws/aws_cloudwatch_log_metric_filter.rb +105 -0
  29. data/lib/resources/aws/aws_config_delivery_channel.rb +74 -0
  30. data/lib/resources/aws/aws_config_recorder.rb +99 -0
  31. data/lib/resources/aws/aws_ebs_volume.rb +127 -0
  32. data/lib/resources/aws/aws_ebs_volumes.rb +69 -0
  33. data/lib/resources/aws/aws_ec2_instance.rb +162 -0
  34. data/lib/resources/aws/aws_ec2_instances.rb +69 -0
  35. data/lib/resources/aws/aws_ecs_cluster.rb +88 -0
  36. data/lib/resources/aws/aws_eks_cluster.rb +105 -0
  37. data/lib/resources/aws/aws_elb.rb +85 -0
  38. data/lib/resources/aws/aws_elbs.rb +84 -0
  39. data/lib/resources/aws/aws_flow_log.rb +106 -0
  40. data/lib/resources/aws/aws_iam_access_key.rb +112 -0
  41. data/lib/resources/aws/aws_iam_access_keys.rb +153 -0
  42. data/lib/resources/aws/aws_iam_group.rb +62 -0
  43. data/lib/resources/aws/aws_iam_groups.rb +56 -0
  44. data/lib/resources/aws/aws_iam_password_policy.rb +121 -0
  45. data/lib/resources/aws/aws_iam_policies.rb +57 -0
  46. data/lib/resources/aws/aws_iam_policy.rb +311 -0
  47. data/lib/resources/aws/aws_iam_role.rb +60 -0
  48. data/lib/resources/aws/aws_iam_root_user.rb +82 -0
  49. data/lib/resources/aws/aws_iam_user.rb +145 -0
  50. data/lib/resources/aws/aws_iam_users.rb +160 -0
  51. data/lib/resources/aws/aws_kms_key.rb +100 -0
  52. data/lib/resources/aws/aws_kms_keys.rb +58 -0
  53. data/lib/resources/aws/aws_rds_instance.rb +74 -0
  54. data/lib/resources/aws/aws_route_table.rb +67 -0
  55. data/lib/resources/aws/aws_route_tables.rb +64 -0
  56. data/lib/resources/aws/aws_s3_bucket.rb +142 -0
  57. data/lib/resources/aws/aws_s3_bucket_object.rb +87 -0
  58. data/lib/resources/aws/aws_s3_buckets.rb +52 -0
  59. data/lib/resources/aws/aws_security_group.rb +314 -0
  60. data/lib/resources/aws/aws_security_groups.rb +71 -0
  61. data/lib/resources/aws/aws_sns_subscription.rb +82 -0
  62. data/lib/resources/aws/aws_sns_topic.rb +57 -0
  63. data/lib/resources/aws/aws_sns_topics.rb +60 -0
  64. data/lib/resources/aws/aws_sqs_queue.rb +66 -0
  65. data/lib/resources/aws/aws_subnet.rb +92 -0
  66. data/lib/resources/aws/aws_subnets.rb +56 -0
  67. data/lib/resources/aws/aws_vpc.rb +77 -0
  68. data/lib/resources/aws/aws_vpcs.rb +55 -0
  69. data/lib/resources/azure/azure_backend.rb +379 -0
  70. data/lib/resources/azure/azure_generic_resource.rb +55 -0
  71. data/lib/resources/azure/azure_resource_group.rb +151 -0
  72. data/lib/resources/azure/azure_virtual_machine.rb +262 -0
  73. data/lib/resources/azure/azure_virtual_machine_data_disk.rb +131 -0
  74. metadata +202 -0
@@ -0,0 +1,314 @@
1
+ require "set"
2
+ require "ipaddr"
3
+
4
+ require "resource_support/aws/aws_singular_resource_mixin"
5
+ require "resource_support/aws/aws_backend_base"
6
+ require "aws-sdk-ec2"
7
+
8
+ class AwsSecurityGroup < Inspec.resource(1)
9
+ name "aws_security_group"
10
+ desc "Verifies settings for an individual AWS Security Group."
11
+ example <<~EXAMPLE
12
+ describe aws_security_group('sg-12345678') do
13
+ it { should exist }
14
+ end
15
+ EXAMPLE
16
+ supports platform: "aws"
17
+
18
+ include AwsSingularResourceMixin
19
+ attr_reader :description, :group_id, :group_name, :vpc_id, :inbound_rules, :outbound_rules, :inbound_rules_count, :outbound_rules_count
20
+
21
+ def to_s
22
+ "EC2 Security Group #{@group_id}"
23
+ end
24
+
25
+ def allow_in?(criteria = {})
26
+ allow(inbound_rules, criteria.dup)
27
+ end
28
+ RSpec::Matchers.alias_matcher :allow_in, :be_allow_in
29
+
30
+ def allow_out?(criteria = {})
31
+ allow(outbound_rules, criteria.dup)
32
+ end
33
+ RSpec::Matchers.alias_matcher :allow_out, :be_allow_out
34
+
35
+ def allow_in_only?(criteria = {})
36
+ allow_only(inbound_rules, criteria.dup)
37
+ end
38
+ RSpec::Matchers.alias_matcher :allow_in_only, :be_allow_in_only
39
+
40
+ def allow_out_only?(criteria = {})
41
+ allow_only(outbound_rules, criteria.dup)
42
+ end
43
+ RSpec::Matchers.alias_matcher :allow_out_only, :be_allow_out_only
44
+
45
+ private
46
+
47
+ def allow_only(rules, criteria)
48
+ rules = allow__focus_on_position(rules, criteria)
49
+ # allow_{in_out}_only require either a single-rule group, or you
50
+ # to select a rule using position.
51
+ return false unless rules.count == 1 || criteria.key?(:position)
52
+
53
+ if criteria.key?(:security_group)
54
+ if criteria.key?(:position)
55
+ pos = criteria[:position] - 1
56
+ else
57
+ pos = 0
58
+ end
59
+ return false unless rules[pos].key?(:user_id_group_pairs) && rules[pos][:user_id_group_pairs].count == 1
60
+ end
61
+ criteria[:exact] = true
62
+ allow(rules, criteria)
63
+ end
64
+
65
+ def allow(rules, criteria)
66
+ criteria = allow__check_criteria(criteria)
67
+ rules = allow__focus_on_position(rules, criteria)
68
+
69
+ rules.any? do |rule|
70
+ matched = true
71
+ matched &&= allow__match_port(rule, criteria)
72
+ matched &&= allow__match_protocol(rule, criteria)
73
+ matched &&= allow__match_ipv4_range(rule, criteria)
74
+ matched &&= allow__match_ipv6_range(rule, criteria)
75
+ matched &&= allow__match_security_group(rule, criteria)
76
+ matched
77
+ end
78
+ end
79
+
80
+ def allow__check_criteria(raw_criteria)
81
+ allowed_criteria = [
82
+ :from_port,
83
+ :ipv4_range,
84
+ :ipv6_range,
85
+ :security_group,
86
+ :port,
87
+ :position,
88
+ :protocol,
89
+ :to_port,
90
+ :exact, # Internal
91
+ ]
92
+ recognized_criteria = {}
93
+ allowed_criteria.each do |expected_criterion|
94
+ if raw_criteria.key?(expected_criterion)
95
+ recognized_criteria[expected_criterion] = raw_criteria.delete(expected_criterion)
96
+ end
97
+ end
98
+
99
+ # Any leftovers are unwelcome
100
+ unless raw_criteria.empty?
101
+ raise ArgumentError, "Unrecognized security group rule 'allow' criteria '#{raw_criteria.keys.join(",")}'. Expected criteria: #{allowed_criteria.join(", ")}"
102
+ end
103
+
104
+ recognized_criteria
105
+ end
106
+
107
+ def allow__focus_on_position(rules, criteria)
108
+ return rules unless criteria.key?(:position)
109
+
110
+ idx = criteria.delete(:position)
111
+
112
+ # Normalize to a zero-based numeric index
113
+ case # rubocop: disable Style/EmptyCaseCondition
114
+ when idx.is_a?(Symbol) && idx == :first
115
+ idx = 0
116
+ when idx.is_a?(Symbol) && idx == :last
117
+ idx = rules.count - 1
118
+ when idx.is_a?(String)
119
+ idx = idx.to_i - 1 # We document this as 1-based, so adjust to be zero-based.
120
+ when idx.is_a?(Numeric)
121
+ idx -= 1 # We document this as 1-based, so adjust to be zero-based.
122
+ else
123
+ raise ArgumentError, "aws_security_group 'allow' 'position' criteria must be an integer or the symbols :first or :last"
124
+ end
125
+
126
+ unless idx < rules.count
127
+ raise ArgumentError, "aws_security_group 'allow' 'position' criteria #{idx + 1} is out of range - there are only #{rules.count} rules for security group #{group_id}."
128
+ end
129
+
130
+ [rules[idx]]
131
+ end
132
+
133
+ def allow__match_port(rule, criteria) # rubocop: disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
134
+ if criteria[:exact] || criteria[:from_port] || criteria[:to_port]
135
+ # Exact match mode
136
+ # :port is shorthand for a single-valued port range.
137
+ criteria[:to_port] = criteria[:from_port] = criteria[:port] if criteria[:port]
138
+ to = criteria[:to_port]
139
+ from = criteria[:from_port]
140
+ # It's a match if neither criteria was specified
141
+ return true if to.nil? && from.nil?
142
+
143
+ # Normalize to integers
144
+ to = to.to_i unless to.nil?
145
+ from = from.to_i unless from.nil?
146
+ # It's a match if either was specified and the other was not
147
+ return true if rule[:to_port] == to && from.nil?
148
+ return true if rule[:from_port] == from && to.nil?
149
+
150
+ # Finally, both must match.
151
+ rule[:to_port] == to && rule[:from_port] == from
152
+ elsif !criteria[:port]
153
+ # port not specified, match anything
154
+ true
155
+ else
156
+ # Range membership mode
157
+ rule_from = rule[:from_port] || 0
158
+ rule_to = rule[:to_port] || 65535
159
+ (rule_from..rule_to).cover?(criteria[:port].to_i)
160
+ end
161
+ end
162
+
163
+ def allow__match_protocol(rule, criteria)
164
+ return true unless criteria.key?(:protocol)
165
+
166
+ prot = criteria[:protocol]
167
+ # We provide a "fluency alias" for -1 (any).
168
+ prot = "-1" if prot == "any"
169
+
170
+ rule[:ip_protocol] == prot
171
+ end
172
+
173
+ def match_ipv4_or_6_range(rule, criteria)
174
+ if criteria.key?(:ipv4_range)
175
+ query = criteria[:ipv4_range]
176
+ query = [query] unless query.is_a?(Array)
177
+ ranges = rule[:ip_ranges].map { |rng| rng[:cidr_ip] }
178
+ else # IPv6
179
+ query = criteria[:ipv6_range]
180
+ query = [query] unless query.is_a?(Array)
181
+ ranges = rule[:ipv_6_ranges].map { |rng| rng[:cidr_ipv_6] }
182
+ end
183
+
184
+ if criteria[:exact]
185
+ Set.new(query) == Set.new(ranges)
186
+ else
187
+ # CIDR subset mode
188
+ # "Each of the provided IP ranges must be a member of one of the rule's listed IP ranges"
189
+ query.all? do |candidate|
190
+ candidate = IPAddr.new(candidate)
191
+ ranges.any? do |range|
192
+ range = IPAddr.new(range)
193
+ range.include?(candidate)
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+ def allow__match_ipv4_range(rule, criteria)
200
+ return true unless criteria.key?(:ipv4_range)
201
+
202
+ match_ipv4_or_6_range(rule, criteria)
203
+ end
204
+
205
+ def allow__match_ipv6_range(rule, criteria)
206
+ return true unless criteria.key?(:ipv6_range)
207
+
208
+ match_ipv4_or_6_range(rule, criteria)
209
+ end
210
+
211
+ def allow__match_security_group(rule, criteria)
212
+ return true unless criteria.key?(:security_group)
213
+
214
+ query = criteria[:security_group]
215
+ return false unless rule[:user_id_group_pairs]
216
+
217
+ rule[:user_id_group_pairs].any? { |group| query == group[:group_id] }
218
+ end
219
+
220
+ def validate_params(raw_params)
221
+ recognized_params = check_resource_param_names(
222
+ raw_params: raw_params,
223
+ allowed_params: %i{id group_id group_name vpc_id},
224
+ allowed_scalar_name: :group_id,
225
+ allowed_scalar_type: String
226
+ )
227
+
228
+ # id is an alias for group_id
229
+ recognized_params[:group_id] = recognized_params.delete(:id) if recognized_params.key?(:id)
230
+
231
+ if recognized_params.key?(:group_id) && recognized_params[:group_id] !~ /^sg\-[0-9a-f]{8}/
232
+ raise ArgumentError, 'aws_security_group security group ID must be in the format "sg-" followed by 8 hexadecimal characters.'
233
+ end
234
+
235
+ if recognized_params.key?(:vpc_id) && recognized_params[:vpc_id] !~ /^vpc\-[0-9a-f]{8}/
236
+ raise ArgumentError, 'aws_security_group VPC ID must be in the format "vpc-" followed by 8 hexadecimal characters.'
237
+ end
238
+
239
+ validated_params = recognized_params
240
+
241
+ if validated_params.empty?
242
+ raise ArgumentError, "You must provide parameters to aws_security_group, such as group_name, group_id, or vpc_id.g_group."
243
+ end
244
+
245
+ validated_params
246
+ end
247
+
248
+ def count_sg_rules(ip_permissions)
249
+ rule_count = 0
250
+ ip_permissions.each do |ip_permission|
251
+ %i{ip_ranges ipv_6_ranges user_id_group_pairs}.each do |key|
252
+ if ip_permission.key? key
253
+ rule_count += ip_permission[key].length
254
+ end
255
+ end
256
+ end
257
+ rule_count
258
+ end
259
+
260
+ def fetch_from_api # rubocop: disable Metrics/AbcSize
261
+ backend = BackendFactory.create(inspec_runner)
262
+
263
+ # Transform into filter format expected by AWS
264
+ filters = []
265
+ %i{
266
+ description
267
+ group_id
268
+ group_name
269
+ vpc_id
270
+ }.each do |criterion_name|
271
+ instance_var = "@#{criterion_name}".to_sym
272
+ next unless instance_variable_defined?(instance_var)
273
+
274
+ val = instance_variable_get(instance_var)
275
+ next if val.nil?
276
+
277
+ filters.push(
278
+ {
279
+ name: criterion_name.to_s.tr("_", "-"),
280
+ values: [val],
281
+ }
282
+ )
283
+ end
284
+ dsg_response = backend.describe_security_groups(filters: filters)
285
+
286
+ if dsg_response.security_groups.empty?
287
+ @exists = false
288
+ @inbound_rules = []
289
+ @outbound_rules = []
290
+ return
291
+ end
292
+
293
+ @exists = true
294
+ @description = dsg_response.security_groups[0].description
295
+ @group_id = dsg_response.security_groups[0].group_id
296
+ @group_name = dsg_response.security_groups[0].group_name
297
+ @vpc_id = dsg_response.security_groups[0].vpc_id
298
+ @inbound_rules = dsg_response.security_groups[0].ip_permissions.map(&:to_h)
299
+ @inbound_rules_count = count_sg_rules(dsg_response.security_groups[0].ip_permissions.map(&:to_h))
300
+ @outbound_rules = dsg_response.security_groups[0].ip_permissions_egress.map(&:to_h)
301
+ @outbound_rules_count = count_sg_rules(dsg_response.security_groups[0].ip_permissions_egress.map(&:to_h))
302
+ end
303
+
304
+ class Backend
305
+ class AwsClientApi < AwsBackendBase
306
+ BackendFactory.set_default_backend self
307
+ self.aws_client_class = Aws::EC2::Client
308
+
309
+ def describe_security_groups(query)
310
+ aws_service_client.describe_security_groups(query)
311
+ end
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,71 @@
1
+ require "resource_support/aws/aws_plural_resource_mixin"
2
+ require "resource_support/aws/aws_backend_base"
3
+ require "aws-sdk-ec2"
4
+
5
+ class AwsSecurityGroups < Inspec.resource(1)
6
+ name "aws_security_groups"
7
+ desc "Verifies settings for AWS Security Groups in bulk"
8
+ example <<~EXAMPLE
9
+ # Verify that you have security groups defined
10
+ describe aws_security_groups do
11
+ it { should exist }
12
+ end
13
+
14
+ # Verify you have more than the default security group
15
+ describe aws_security_groups do
16
+ its('entries.count') { should be > 1 }
17
+ end
18
+ EXAMPLE
19
+ supports platform: "aws"
20
+
21
+ include AwsPluralResourceMixin
22
+
23
+ # Underlying FilterTable implementation.
24
+ filter = FilterTable.create
25
+ filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
26
+ filter.register_column(:group_ids, field: :group_id)
27
+ filter.install_filter_methods_on_resource(self, :table)
28
+
29
+ def to_s
30
+ "EC2 Security Groups"
31
+ end
32
+
33
+ private
34
+
35
+ def validate_params(raw_criteria)
36
+ unless raw_criteria.is_a? Hash
37
+ raise "Unrecognized criteria for fetching Security Groups. " \
38
+ "Use 'criteria: value' format."
39
+ end
40
+
41
+ # No criteria yet
42
+ unless raw_criteria.empty?
43
+ raise ArgumentError, "aws_ec2_security_groups does not currently accept resource parameters."
44
+ end
45
+
46
+ raw_criteria
47
+ end
48
+
49
+ def fetch_from_api
50
+ @table = []
51
+ backend = BackendFactory.create(inspec_runner)
52
+ backend.describe_security_groups({}).security_groups.each do |sg_info|
53
+ @table.push({
54
+ group_id: sg_info.group_id,
55
+ group_name: sg_info.group_name,
56
+ vpc_id: sg_info.vpc_id,
57
+ })
58
+ end
59
+ end
60
+
61
+ class Backend
62
+ class AwsClientApi < AwsBackendBase
63
+ BackendFactory.set_default_backend self
64
+ self.aws_client_class = Aws::EC2::Client
65
+
66
+ def describe_security_groups(query)
67
+ aws_service_client.describe_security_groups(query)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,82 @@
1
+ require "resource_support/aws/aws_singular_resource_mixin"
2
+ require "resource_support/aws/aws_backend_base"
3
+ require "aws-sdk-sns"
4
+
5
+ class AwsSnsSubscription < Inspec.resource(1)
6
+ name "aws_sns_subscription"
7
+ desc "Verifies settings for an SNS Subscription"
8
+ example <<~EXAMPLE
9
+ describe aws_sns_subscription('arn:aws:sns:us-east-1::test-topic-01:b214aff5-a2c7-438f-a753-8494493f2ff6') do
10
+ it { should_not have_raw_message_delivery }
11
+ it { should be_confirmation_authenticated }
12
+ its('owner') { should cmp '12345678' }
13
+ its('topic_arn') { should cmp 'arn:aws:sns:us-east-1::test-topic-01' }
14
+ its('endpoint') { should cmp 'arn:aws:sqs:us-east-1::test-queue-01' }
15
+ its('protocol') { should cmp 'sqs' }
16
+ end
17
+ EXAMPLE
18
+
19
+ supports platform: "aws"
20
+
21
+ include AwsSingularResourceMixin
22
+ attr_reader :arn, :owner, :raw_message_delivery, :topic_arn, :endpoint, :protocol,
23
+ :confirmation_was_authenticated, :aws_response
24
+
25
+ alias confirmation_authenticated? confirmation_was_authenticated
26
+ alias raw_message_delivery? raw_message_delivery
27
+
28
+ def has_raw_message_delivery?
29
+ raw_message_delivery
30
+ end
31
+
32
+ def to_s
33
+ "SNS Subscription #{@arn}"
34
+ end
35
+
36
+ private
37
+
38
+ def validate_params(raw_params)
39
+ validated_params = check_resource_param_names(
40
+ raw_params: raw_params,
41
+ allowed_params: [:subscription_arn],
42
+ allowed_scalar_name: :subscription_arn,
43
+ allowed_scalar_type: String
44
+ )
45
+
46
+ if validated_params.empty?
47
+ raise ArgumentError, "You must provide a subscription_arn to aws_sns_subscription."
48
+ end
49
+
50
+ validated_params
51
+ end
52
+
53
+ def fetch_from_api
54
+ backend = BackendFactory.create(inspec_runner)
55
+ catch_aws_errors do
56
+ begin
57
+ aws_response = backend.get_subscription_attributes(subscription_arn: @subscription_arn).attributes
58
+ @exists = true
59
+ @owner = aws_response["Owner"]
60
+ @raw_message_delivery = aws_response["RawMessageDelivery"].eql?("true")
61
+ @topic_arn = aws_response["TopicArn"]
62
+ @endpoint = aws_response["Endpoint"]
63
+ @protocol = aws_response["Protocol"]
64
+ @confirmation_was_authenticated = aws_response["ConfirmationWasAuthenticated"].eql?("true")
65
+ rescue Aws::SNS::Errors::NotFound
66
+ @exists = false
67
+ return
68
+ end
69
+ end
70
+ end
71
+
72
+ class Backend
73
+ class AwsClientApi < AwsBackendBase
74
+ BackendFactory.set_default_backend self
75
+ self.aws_client_class = Aws::SNS::Client
76
+
77
+ def get_subscription_attributes(criteria)
78
+ aws_service_client.get_subscription_attributes(criteria)
79
+ end
80
+ end
81
+ end
82
+ end