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,57 @@
1
+ require "resource_support/aws/aws_plural_resource_mixin"
2
+ require "resource_support/aws/aws_backend_base"
3
+ require "aws-sdk-iam"
4
+
5
+ class AwsIamPolicies < Inspec.resource(1)
6
+ name "aws_iam_policies"
7
+ desc "Verifies settings for AWS IAM Policies in bulk"
8
+ example <<~EXAMPLE
9
+ describe aws_iam_policies do
10
+ it { should exist }
11
+ end
12
+ EXAMPLE
13
+ supports platform: "aws"
14
+
15
+ include AwsPluralResourceMixin
16
+ def validate_params(resource_params)
17
+ unless resource_params.empty?
18
+ raise ArgumentError, "aws_iam_policies does not accept resource parameters."
19
+ end
20
+
21
+ resource_params
22
+ end
23
+
24
+ # Underlying FilterTable implementation.
25
+ filter = FilterTable.create
26
+ filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
27
+ filter.register_column(:policy_names, field: :policy_name)
28
+ .register_column(:arns, field: :arn)
29
+ filter.install_filter_methods_on_resource(self, :table)
30
+
31
+ def to_s
32
+ "IAM Policies"
33
+ end
34
+
35
+ def fetch_from_api
36
+ backend = BackendFactory.create(inspec_runner)
37
+ @table = []
38
+ pagination_opts = {}
39
+ loop do
40
+ api_result = backend.list_policies(pagination_opts)
41
+ @table += api_result.policies.map(&:to_h)
42
+ pagination_opts = { marker: api_result.marker }
43
+ break unless api_result.is_truncated
44
+ end
45
+ end
46
+
47
+ class Backend
48
+ class AwsClientApi < AwsBackendBase
49
+ BackendFactory.set_default_backend(self)
50
+ self.aws_client_class = Aws::IAM::Client
51
+
52
+ def list_policies(query)
53
+ aws_service_client.list_policies(query)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,311 @@
1
+ require "resource_support/aws/aws_singular_resource_mixin"
2
+ require "resource_support/aws/aws_backend_base"
3
+ require "aws-sdk-iam"
4
+
5
+ require "json"
6
+ require "set"
7
+ require "uri"
8
+
9
+ class AwsIamPolicy < Inspec.resource(1)
10
+ name "aws_iam_policy"
11
+ desc "Verifies settings for individual AWS IAM Policy"
12
+ example <<~EXAMPLE
13
+ describe aws_iam_policy('AWSSupportAccess') do
14
+ it { should be_attached }
15
+ end
16
+ EXAMPLE
17
+ supports platform: "aws"
18
+
19
+ include AwsSingularResourceMixin
20
+
21
+ attr_reader :arn, :attachment_count, :default_version_id
22
+
23
+ # Note that we also accept downcases and symbol versions of these
24
+ EXPECTED_CRITERIA = %w{
25
+ Action
26
+ Effect
27
+ Resource
28
+ Sid
29
+ }.freeze
30
+
31
+ UNIMPLEMENTED_CRITERIA = %w{
32
+ Conditional
33
+ NotAction
34
+ NotPrincipal
35
+ NotResource
36
+ Principal
37
+ }.freeze
38
+
39
+ def to_s
40
+ "Policy #{@policy_name}"
41
+ end
42
+
43
+ def attached?
44
+ attachment_count > 0
45
+ end
46
+
47
+ def attached_users
48
+ return @attached_users if defined? @attached_users
49
+
50
+ fetch_attached_entities
51
+ @attached_users
52
+ end
53
+
54
+ def attached_groups
55
+ return @attached_groups if defined? @attached_groups
56
+
57
+ fetch_attached_entities
58
+ @attached_groups
59
+ end
60
+
61
+ def attached_roles
62
+ return @attached_roles if defined? @attached_roles
63
+
64
+ fetch_attached_entities
65
+ @attached_roles
66
+ end
67
+
68
+ def attached_to_user?(user_name)
69
+ attached_users.include?(user_name)
70
+ end
71
+
72
+ def attached_to_group?(group_name)
73
+ attached_groups.include?(group_name)
74
+ end
75
+
76
+ def attached_to_role?(role_name)
77
+ attached_roles.include?(role_name)
78
+ end
79
+
80
+ def policy
81
+ return nil unless exists?
82
+ return @policy if defined?(@policy)
83
+
84
+ catch_aws_errors do
85
+ backend = BackendFactory.create(inspec_runner)
86
+ gpv_response = backend.get_policy_version(policy_arn: arn, version_id: default_version_id)
87
+ @policy = JSON.parse(URI.decode_www_form_component(gpv_response.policy_version.document))
88
+ end
89
+ @policy
90
+ end
91
+
92
+ def statement_count
93
+ return nil unless exists?
94
+
95
+ # Typically it is an array of statements
96
+ if policy["Statement"].is_a? Array
97
+ policy["Statement"].count
98
+ else
99
+ # But if there is one statement, it is permissable to degenerate the array,
100
+ # and place the statement as a hash directly under the 'Statement' key
101
+ return 1
102
+ end
103
+ end
104
+
105
+ def has_statement?(provided_criteria = {})
106
+ return nil unless exists?
107
+
108
+ raw_criteria = provided_criteria.dup # provided_criteria is used for output formatting - can't delete from it.
109
+ criteria = has_statement__validate_criteria(raw_criteria)
110
+ @normalized_statements ||= has_statement__normalize_statements
111
+ statements = has_statement__focus_on_sid(@normalized_statements, criteria)
112
+ statements.any? do |statement|
113
+ true && \
114
+ has_statement__effect(statement, criteria) && \
115
+ has_statement__array_criterion(:action, statement, criteria) && \
116
+ has_statement__array_criterion(:resource, statement, criteria)
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def has_statement__validate_criteria(raw_criteria)
123
+ recognized_criteria = {}
124
+ EXPECTED_CRITERIA.each do |expected_criterion|
125
+ [
126
+ expected_criterion,
127
+ expected_criterion.downcase,
128
+ expected_criterion.to_sym,
129
+ expected_criterion.downcase.to_sym,
130
+ ].each do |variant|
131
+ if raw_criteria.key?(variant)
132
+ # Always store as downcased symbol
133
+ recognized_criteria[expected_criterion.downcase.to_sym] = raw_criteria.delete(variant)
134
+ end
135
+ end
136
+ end
137
+
138
+ # Special message for valid, but unimplemented statement attributes
139
+ UNIMPLEMENTED_CRITERIA.each do |unimplemented_criterion|
140
+ [
141
+ unimplemented_criterion,
142
+ unimplemented_criterion.downcase,
143
+ unimplemented_criterion.to_sym,
144
+ unimplemented_criterion.downcase.to_sym,
145
+ ].each do |variant|
146
+ if raw_criteria.key?(variant)
147
+ raise ArgumentError, "Criterion '#{unimplemented_criterion}' is not supported for performing have_statement queries."
148
+ end
149
+ end
150
+ end
151
+
152
+ # If anything is left, it's spurious
153
+ unless raw_criteria.empty?
154
+ raise ArgumentError, "Unrecognized criteria #{raw_criteria.keys.join(", ")} to have_statement. Recognized criteria: #{EXPECTED_CRITERIA.join(", ")}"
155
+ end
156
+
157
+ # Effect has only 2 permitted values
158
+ if recognized_criteria.key?(:effect)
159
+ unless %w{Allow Deny}.include?(recognized_criteria[:effect])
160
+ raise ArgumentError, "Criterion 'Effect' for have_statement must be one of 'Allow' or 'Deny' - got '#{recognized_criteria[:effect]}'"
161
+ end
162
+ end
163
+
164
+ recognized_criteria
165
+ end
166
+
167
+ def has_statement__normalize_statements
168
+ # Some single-statement policies place their statement
169
+ # directly in policy['Statement'], rather than in an
170
+ # Array within it. See arn:aws:iam::aws:policy/AWSCertificateManagerReadOnly
171
+ # Thus, coerce to Array.
172
+ policy["Statement"] = [policy["Statement"]] if policy["Statement"].is_a? Hash
173
+ policy["Statement"].map do |statement|
174
+ # Coerce some values into arrays
175
+ %w{Action Resource}.each do |field|
176
+ if statement.key?(field)
177
+ statement[field] = Array(statement[field])
178
+ end
179
+ end
180
+
181
+ # Symbolize all keys
182
+ statement.keys.each do |field|
183
+ statement[field.downcase.to_sym] = statement.delete(field)
184
+ end
185
+
186
+ statement
187
+ end
188
+ end
189
+
190
+ def has_statement__focus_on_sid(statements, criteria)
191
+ return statements unless criteria.key?(:sid)
192
+
193
+ sid_seek = criteria[:sid]
194
+ statements.select do |statement|
195
+ if sid_seek.is_a? Regexp
196
+ statement[:sid] =~ sid_seek
197
+ else
198
+ statement[:sid] == sid_seek
199
+ end
200
+ end
201
+ end
202
+
203
+ def has_statement__effect(statement, criteria)
204
+ !criteria.key?(:effect) || criteria[:effect] == statement[:effect]
205
+ end
206
+
207
+ def has_statement__array_criterion(crit_name, statement, criteria)
208
+ return true unless criteria.key?(crit_name)
209
+
210
+ check = criteria[crit_name]
211
+ # This is an array due to normalize_statements
212
+ # If it is nil, the statement does not have an entry for that dimension;
213
+ # but since we were asked to match on it (on nothing), we
214
+ # decide to never match
215
+ values = statement[crit_name]
216
+ return false if values.nil?
217
+
218
+ if check.is_a?(String)
219
+ # If check is a string, it only has to match one of the values
220
+ values.any? { |v| v == check }
221
+ elsif check.is_a?(Regexp)
222
+ # If check is a regex, it only has to match one of the values
223
+ values.any? { |v| v =~ check }
224
+ elsif check.is_a?(Array) && check.all? { |c| c.is_a? String }
225
+ # If check is an array of strings, perform setwise check
226
+ Set.new(values) == Set.new(check)
227
+ elsif check.is_a?(Array) && check.all? { |c| c.is_a? Regexp }
228
+ # If check is an array of regexes, all values must match all regexes
229
+ values.all? { |v| check.all? { |r| v =~ r } }
230
+ else
231
+ false
232
+ end
233
+ end
234
+
235
+ def validate_params(raw_params)
236
+ validated_params = check_resource_param_names(
237
+ raw_params: raw_params,
238
+ allowed_params: [:policy_name],
239
+ allowed_scalar_name: :policy_name,
240
+ allowed_scalar_type: String
241
+ )
242
+
243
+ if validated_params.empty?
244
+ raise ArgumentError, "You must provide the parameter 'policy_name' to aws_iam_policy."
245
+ end
246
+
247
+ validated_params
248
+ end
249
+
250
+ def fetch_from_api
251
+ backend = BackendFactory.create(inspec_runner)
252
+
253
+ policy = nil
254
+ pagination_opts = { max_items: 1000 }
255
+ loop do
256
+ api_result = backend.list_policies(pagination_opts)
257
+ policy = api_result.policies.detect do |p|
258
+ p.policy_name == @policy_name
259
+ end
260
+ break if policy # Found it!
261
+ break unless api_result.is_truncated # Not found and no more results
262
+
263
+ pagination_opts[:marker] = api_result.marker
264
+ end
265
+
266
+ @exists = !policy.nil?
267
+
268
+ return unless @exists
269
+
270
+ @arn = policy[:arn]
271
+ @default_version_id = policy[:default_version_id]
272
+ @attachment_count = policy[:attachment_count]
273
+ end
274
+
275
+ def fetch_attached_entities
276
+ unless @exists
277
+ @attached_groups = nil
278
+ @attached_users = nil
279
+ @attached_roles = nil
280
+ return
281
+ end
282
+ backend = AwsIamPolicy::BackendFactory.create(inspec_runner)
283
+ criteria = { policy_arn: arn }
284
+ resp = nil
285
+ catch_aws_errors do
286
+ resp = backend.list_entities_for_policy(criteria)
287
+ end
288
+ @attached_groups = resp.policy_groups.map(&:group_name)
289
+ @attached_users = resp.policy_users.map(&:user_name)
290
+ @attached_roles = resp.policy_roles.map(&:role_name)
291
+ end
292
+
293
+ class Backend
294
+ class AwsClientApi < AwsBackendBase
295
+ BackendFactory.set_default_backend(self)
296
+ self.aws_client_class = Aws::IAM::Client
297
+
298
+ def get_policy_version(criteria)
299
+ aws_service_client.get_policy_version(criteria)
300
+ end
301
+
302
+ def list_policies(criteria)
303
+ aws_service_client.list_policies(criteria)
304
+ end
305
+
306
+ def list_entities_for_policy(criteria)
307
+ aws_service_client.list_entities_for_policy(criteria)
308
+ end
309
+ end
310
+ end
311
+ end
@@ -0,0 +1,60 @@
1
+ require "resource_support/aws/aws_singular_resource_mixin"
2
+ require "resource_support/aws/aws_backend_base"
3
+ require "aws-sdk-iam"
4
+
5
+ class AwsIamRole < Inspec.resource(1)
6
+ name "aws_iam_role"
7
+ desc "Verifies settings for an IAM Role"
8
+ example <<~EXAMPLE
9
+ describe aws_iam_role('my-role') do
10
+ it { should exist }
11
+ end
12
+ EXAMPLE
13
+ supports platform: "aws"
14
+
15
+ include AwsSingularResourceMixin
16
+ attr_reader :description, :role_name
17
+
18
+ def to_s
19
+ "IAM Role #{role_name}"
20
+ end
21
+
22
+ private
23
+
24
+ def validate_params(raw_params)
25
+ validated_params = check_resource_param_names(
26
+ raw_params: raw_params,
27
+ allowed_params: [:role_name],
28
+ allowed_scalar_name: :role_name,
29
+ allowed_scalar_type: String
30
+ )
31
+ if validated_params.empty?
32
+ raise ArgumentError, "You must provide a role_name to aws_iam_role."
33
+ end
34
+
35
+ validated_params
36
+ end
37
+
38
+ def fetch_from_api
39
+ role_info = nil
40
+ begin
41
+ role_info = BackendFactory.create(inspec_runner).get_role(role_name: role_name)
42
+ rescue Aws::IAM::Errors::NoSuchEntity
43
+ @exists = false
44
+ return
45
+ end
46
+ @exists = true
47
+ @description = role_info.role.description
48
+ end
49
+
50
+ # Uses the SDK API to really talk to AWS
51
+ class Backend
52
+ class AwsClientApi < AwsBackendBase
53
+ BackendFactory.set_default_backend(self)
54
+ self.aws_client_class = Aws::IAM::Client
55
+ def get_role(query)
56
+ aws_service_client.get_role(query)
57
+ end
58
+ end
59
+ end
60
+ 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-iam"
4
+
5
+ class AwsIamRootUser < Inspec.resource(1)
6
+ name "aws_iam_root_user"
7
+ desc "Verifies settings for AWS root account"
8
+ example <<~EXAMPLE
9
+ describe aws_iam_root_user do
10
+ it { should have_access_key }
11
+ end
12
+ EXAMPLE
13
+ supports platform: "aws"
14
+
15
+ # TODO: rewrite to avoid direct injection, match other resources, use AwsSingularResourceMixin
16
+ def initialize(conn = nil)
17
+ @client = conn ? conn.iam_client : inspec_runner.backend.aws_client(Aws::IAM::Client)
18
+ end
19
+
20
+ # TODO: DRY up, see https://github.com/chef/inspec/issues/2633
21
+ # Copied from resource_support/aws/aws_resource_mixin.rb
22
+ def catch_aws_errors
23
+ yield
24
+ rescue Aws::Errors::MissingCredentialsError
25
+ # The AWS error here is unhelpful:
26
+ # "unable to sign request without credentials set"
27
+ Inspec::Log.error "It appears that you have not set your AWS credentials. You may set them using environment variables, or using the 'aws://region/aws_credentials_profile' target. See https://www.inspec.io/docs/reference/platforms for details."
28
+ fail_resource("No AWS credentials available")
29
+ rescue Aws::Errors::ServiceError => e
30
+ fail_resource e.message
31
+ end
32
+
33
+ # TODO: DRY up, see https://github.com/chef/inspec/issues/2633
34
+ # Copied from resource_support/aws/aws_singular_resource_mixin.rb
35
+ def inspec_runner
36
+ # When running under inspec-cli, we have an 'inspec' method that
37
+ # returns the runner. When running under unit tests, we don't
38
+ # have that, but we still have to call this to pass something
39
+ # (nil is OK) to the backend.
40
+ # TODO: remove with https://github.com/chef/inspec-aws/issues/216
41
+ # TODO: remove after rewrite to include AwsSingularResource
42
+ inspec if respond_to?(:inspec)
43
+ end
44
+
45
+ def has_access_key?
46
+ summary_account["AccountAccessKeysPresent"] == 1
47
+ end
48
+
49
+ def has_mfa_enabled?
50
+ summary_account["AccountMFAEnabled"] == 1
51
+ end
52
+
53
+ # if the root account has a Virtual MFA device then it will have a special
54
+ # serial number ending in 'root-account-mfa-device'
55
+ def has_virtual_mfa_enabled?
56
+ mfa_device_pattern = %r{arn:aws:iam::\d{12}:mfa\/root-account-mfa-device}
57
+
58
+ virtual_mfa_devices.any? { |d| mfa_device_pattern =~ d["serial_number"] }
59
+ end
60
+
61
+ def has_hardware_mfa_enabled?
62
+ has_mfa_enabled? && !has_virtual_mfa_enabled?
63
+ end
64
+
65
+ def to_s
66
+ "AWS Root-User"
67
+ end
68
+
69
+ private
70
+
71
+ def summary_account
72
+ catch_aws_errors do
73
+ @summary_account ||= @client.get_account_summary.summary_map
74
+ end
75
+ end
76
+
77
+ def virtual_mfa_devices
78
+ catch_aws_errors do
79
+ @__virtual_devices ||= @client.list_virtual_mfa_devices.virtual_mfa_devices
80
+ end
81
+ end
82
+ end