inspec 4.22.1

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