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.
- checksums.yaml +7 -0
- data/Gemfile +63 -0
- data/inspec.gemspec +36 -0
- data/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/Gemfile +11 -0
- data/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/inspec-plugin-template.gemspec +43 -0
- data/lib/plugins/inspec-init/templates/profiles/aws/README.md +192 -0
- data/lib/plugins/inspec-init/templates/profiles/aws/attributes.yml +2 -0
- data/lib/plugins/inspec-init/templates/profiles/aws/controls/example.rb +39 -0
- data/lib/plugins/inspec-init/templates/profiles/aws/inspec.yml +22 -0
- data/lib/plugins/inspec-init/templates/profiles/azure/README.md +56 -0
- data/lib/plugins/inspec-init/templates/profiles/azure/controls/example.rb +14 -0
- data/lib/plugins/inspec-init/templates/profiles/azure/inspec.yml +14 -0
- data/lib/plugins/inspec-init/templates/profiles/gcp/README.md +66 -0
- data/lib/plugins/inspec-init/templates/profiles/gcp/attributes.yml +2 -0
- data/lib/plugins/inspec-init/templates/profiles/gcp/controls/example.rb +27 -0
- data/lib/plugins/inspec-init/templates/profiles/gcp/inspec.yml +19 -0
- data/lib/resource_support/aws.rb +76 -0
- data/lib/resource_support/aws/aws_backend_base.rb +12 -0
- data/lib/resource_support/aws/aws_backend_factory_mixin.rb +12 -0
- data/lib/resource_support/aws/aws_plural_resource_mixin.rb +24 -0
- data/lib/resource_support/aws/aws_resource_mixin.rb +69 -0
- data/lib/resource_support/aws/aws_singular_resource_mixin.rb +27 -0
- data/lib/resources/aws/aws_billing_report.rb +107 -0
- data/lib/resources/aws/aws_billing_reports.rb +74 -0
- data/lib/resources/aws/aws_cloudtrail_trail.rb +97 -0
- data/lib/resources/aws/aws_cloudtrail_trails.rb +51 -0
- data/lib/resources/aws/aws_cloudwatch_alarm.rb +67 -0
- data/lib/resources/aws/aws_cloudwatch_log_metric_filter.rb +105 -0
- data/lib/resources/aws/aws_config_delivery_channel.rb +74 -0
- data/lib/resources/aws/aws_config_recorder.rb +99 -0
- data/lib/resources/aws/aws_ebs_volume.rb +127 -0
- data/lib/resources/aws/aws_ebs_volumes.rb +69 -0
- data/lib/resources/aws/aws_ec2_instance.rb +162 -0
- data/lib/resources/aws/aws_ec2_instances.rb +69 -0
- data/lib/resources/aws/aws_ecs_cluster.rb +88 -0
- data/lib/resources/aws/aws_eks_cluster.rb +105 -0
- data/lib/resources/aws/aws_elb.rb +85 -0
- data/lib/resources/aws/aws_elbs.rb +84 -0
- data/lib/resources/aws/aws_flow_log.rb +106 -0
- data/lib/resources/aws/aws_iam_access_key.rb +112 -0
- data/lib/resources/aws/aws_iam_access_keys.rb +153 -0
- data/lib/resources/aws/aws_iam_group.rb +62 -0
- data/lib/resources/aws/aws_iam_groups.rb +56 -0
- data/lib/resources/aws/aws_iam_password_policy.rb +121 -0
- data/lib/resources/aws/aws_iam_policies.rb +57 -0
- data/lib/resources/aws/aws_iam_policy.rb +311 -0
- data/lib/resources/aws/aws_iam_role.rb +60 -0
- data/lib/resources/aws/aws_iam_root_user.rb +82 -0
- data/lib/resources/aws/aws_iam_user.rb +145 -0
- data/lib/resources/aws/aws_iam_users.rb +160 -0
- data/lib/resources/aws/aws_kms_key.rb +100 -0
- data/lib/resources/aws/aws_kms_keys.rb +58 -0
- data/lib/resources/aws/aws_rds_instance.rb +74 -0
- data/lib/resources/aws/aws_route_table.rb +67 -0
- data/lib/resources/aws/aws_route_tables.rb +64 -0
- data/lib/resources/aws/aws_s3_bucket.rb +142 -0
- data/lib/resources/aws/aws_s3_bucket_object.rb +87 -0
- data/lib/resources/aws/aws_s3_buckets.rb +52 -0
- data/lib/resources/aws/aws_security_group.rb +314 -0
- data/lib/resources/aws/aws_security_groups.rb +71 -0
- data/lib/resources/aws/aws_sns_subscription.rb +82 -0
- data/lib/resources/aws/aws_sns_topic.rb +57 -0
- data/lib/resources/aws/aws_sns_topics.rb +60 -0
- data/lib/resources/aws/aws_sqs_queue.rb +66 -0
- data/lib/resources/aws/aws_subnet.rb +92 -0
- data/lib/resources/aws/aws_subnets.rb +56 -0
- data/lib/resources/aws/aws_vpc.rb +77 -0
- data/lib/resources/aws/aws_vpcs.rb +55 -0
- data/lib/resources/azure/azure_backend.rb +379 -0
- data/lib/resources/azure/azure_generic_resource.rb +55 -0
- data/lib/resources/azure/azure_resource_group.rb +151 -0
- data/lib/resources/azure/azure_virtual_machine.rb +262 -0
- data/lib/resources/azure/azure_virtual_machine_data_disk.rb +131 -0
- metadata +202 -0
@@ -0,0 +1,379 @@
|
|
1
|
+
# Base class for Azure Resources. This allows the generic class to work
|
2
|
+
# as well as the specific target resources for Azure Resources
|
3
|
+
#
|
4
|
+
# @author Russell Seymour
|
5
|
+
module Inspec::Resources
|
6
|
+
class AzureResourceBase < Inspec.resource(1)
|
7
|
+
attr_reader :opts, :client, :azure
|
8
|
+
|
9
|
+
# Constructor that retreives the specified resource
|
10
|
+
#
|
11
|
+
# The opts hash should contain the following
|
12
|
+
# :group_name - name of the resource group in which to look for items
|
13
|
+
# :type - the type of Azure resource to look for
|
14
|
+
# :apiversion - API version to use when looking for a specific resource
|
15
|
+
# :name - name of the resource to find
|
16
|
+
#
|
17
|
+
# @author Russell Seymour
|
18
|
+
#
|
19
|
+
# @param [Hash] opts Hashtable of options as highlighted above
|
20
|
+
# rubocop:disable Metrics/AbcSize
|
21
|
+
def initialize(opts)
|
22
|
+
# declare the hashtable of counts
|
23
|
+
@counts = {}
|
24
|
+
@total = 0
|
25
|
+
@opts = opts
|
26
|
+
|
27
|
+
# Determine if the environment variables for the options have been set
|
28
|
+
option_var_names = {
|
29
|
+
group_name: "AZURE_RESOURCE_GROUP_NAME",
|
30
|
+
name: "AZURE_RESOURCE_NAME",
|
31
|
+
type: "AZURE_RESOURCE_TYPE",
|
32
|
+
apiversion: "AZURE_RESOURCE_API_VERSION",
|
33
|
+
}
|
34
|
+
option_var_names.each do |option_name, env_var_name|
|
35
|
+
opts[option_name] = ENV[env_var_name] unless ENV[env_var_name].nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
@azure = inspec.backend
|
39
|
+
@client = azure.azure_client
|
40
|
+
@failed_resource = false
|
41
|
+
end
|
42
|
+
|
43
|
+
def failed_resource?
|
44
|
+
@failed_resource
|
45
|
+
end
|
46
|
+
|
47
|
+
def catch_azure_errors
|
48
|
+
yield
|
49
|
+
rescue MsRestAzure::AzureOperationError => e
|
50
|
+
# e.message is actually a massive stringified JSON, which might be useful in the future.
|
51
|
+
# You want error_message here.
|
52
|
+
fail_resource e.error_message
|
53
|
+
@failed_resource = true
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# Return information about the resource group
|
58
|
+
def resource_group
|
59
|
+
catch_azure_errors do
|
60
|
+
resource_group = client.resource_groups.get(opts[:group_name])
|
61
|
+
|
62
|
+
# create the methods for the resource group object
|
63
|
+
dm = AzureResourceDynamicMethods.new
|
64
|
+
dm.create_methods(self, resource_group)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def resources
|
69
|
+
resources = nil
|
70
|
+
catch_azure_errors do
|
71
|
+
resources = client.resources.list_by_resource_group(opts[:group_name])
|
72
|
+
end
|
73
|
+
return if failed_resource?
|
74
|
+
|
75
|
+
# filter the resources based on the type, and the name if they been specified
|
76
|
+
resources = filter_resources(resources, opts)
|
77
|
+
|
78
|
+
# if there is one resource then define methods on this class
|
79
|
+
if resources.count == 1
|
80
|
+
@total = 1
|
81
|
+
|
82
|
+
resource = nil
|
83
|
+
catch_azure_errors do
|
84
|
+
# get the apiversion for the resource, if one has not been specified
|
85
|
+
apiversion = azure.get_api_version(resources[0].type, opts)
|
86
|
+
|
87
|
+
# get the resource by id so it can be interrogated
|
88
|
+
resource = client.resources.get_by_id(resources[0].id, apiversion)
|
89
|
+
end
|
90
|
+
return if failed_resource?
|
91
|
+
|
92
|
+
dm = AzureResourceDynamicMethods.new
|
93
|
+
|
94
|
+
dm.create_methods(self, resource)
|
95
|
+
else
|
96
|
+
|
97
|
+
# As there are many resources, parse each one so that it can be
|
98
|
+
# interrogated by the FilterTable
|
99
|
+
# @probes = parse_resources(resources, azure)
|
100
|
+
@probes = resources.each.map do |item|
|
101
|
+
# update the total
|
102
|
+
@total += 1
|
103
|
+
|
104
|
+
# determine the counts for each type
|
105
|
+
namespace, type_name = item.type.split(/\./)
|
106
|
+
counts.key?(namespace) ? false : counts[namespace] = {}
|
107
|
+
counts[namespace].key?(type_name) ? counts[namespace][type_name] += 1 : counts[namespace][type_name] = 1
|
108
|
+
|
109
|
+
# get the detail about the resource
|
110
|
+
apiversion = azure.get_api_version(item.type, opts)
|
111
|
+
resource = client.resources.get_by_id(item.id, apiversion)
|
112
|
+
|
113
|
+
# parse the resource
|
114
|
+
parse_resource(resource)
|
115
|
+
end.compact
|
116
|
+
|
117
|
+
# Iterate around the counts and create the necessary classes
|
118
|
+
counts.each do |namespace, ns_counts|
|
119
|
+
define_singleton_method namespace do
|
120
|
+
AzureResourceTypeCounts.new(ns_counts)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Does the resource have any tags?
|
127
|
+
#
|
128
|
+
# If it is a Hashtable then it does not, because there was nothing to parse so there is not
|
129
|
+
# a nested object to work with
|
130
|
+
#
|
131
|
+
# @author Russell Seymour
|
132
|
+
def has_tags?
|
133
|
+
tags.is_a?(Hash) ? false : true
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns how many tags have been set on the resource
|
137
|
+
#
|
138
|
+
# @author Russell Seymour
|
139
|
+
def tag_count
|
140
|
+
tags.count
|
141
|
+
end
|
142
|
+
|
143
|
+
# It is necessary to be able to test the tags of a resource. It is possible to say of the
|
144
|
+
# resource has tags or not, and it is possible to check that the tags include a specific tag
|
145
|
+
# However the value is not accessible, this function creates methods for all the tags that
|
146
|
+
# are available.
|
147
|
+
#
|
148
|
+
# The format of the method name is '<TAG_NAME>_tag' and will return the value of that tag
|
149
|
+
#
|
150
|
+
# Disabling rubopcop check. If this is set as a normal if..then..end statement there is a
|
151
|
+
# violation stating it should use a guard. When using a guard it throws this error
|
152
|
+
#
|
153
|
+
# @author Russell Seymour
|
154
|
+
def create_tag_methods
|
155
|
+
# Iterate around the items of the tags and create the necessary access methods
|
156
|
+
if defined?(tags.item)
|
157
|
+
tags.item.each do |name, value|
|
158
|
+
method_name = format("%s_tag", name)
|
159
|
+
define_singleton_method method_name do
|
160
|
+
value
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
# Filter the resources that are returned by the options that have been specified
|
169
|
+
#
|
170
|
+
def filter_resources(resources, opts)
|
171
|
+
if opts[:type] && opts[:name]
|
172
|
+
resources.select { |r| r.type == opts[:type] && r.name == opts[:name] }
|
173
|
+
elsif opts[:type]
|
174
|
+
resources.select { |r| r.type == opts[:type] }
|
175
|
+
elsif opts[:name]
|
176
|
+
resources.select { |r| r.name == opts[:name] }
|
177
|
+
else
|
178
|
+
resources
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Class to create methods on the calling object at run time.
|
185
|
+
# Each of the Azure Resources have different attributes and properties, and they all need
|
186
|
+
# to be testable. To do this no methods are hardcoded, each on is craeted based on the
|
187
|
+
# information returned from Azure.
|
188
|
+
#
|
189
|
+
# The class is a helper class essentially as it creates the methods on the calling class
|
190
|
+
# rather than itself. This means that there is less duplication of code and it can be
|
191
|
+
# reused easily.
|
192
|
+
#
|
193
|
+
# @author Russell Seymour
|
194
|
+
# @since 0.2.0
|
195
|
+
class AzureResourceDynamicMethods
|
196
|
+
# Given the calling object and its data, create the methods on the object according
|
197
|
+
# to the data that has been retrieved. Various types of data can be returned so the method
|
198
|
+
# checks the type to ensure that the necessary methods are configured correctly
|
199
|
+
#
|
200
|
+
# @param AzureResourceProbe|AzureResource object The object on which the methods should be craeted
|
201
|
+
# @param variant data The data from which the methods should be created
|
202
|
+
def create_methods(object, data)
|
203
|
+
# Check the type of data as this affects the setup of the methods
|
204
|
+
# If it is an Azure Generic Resource then setup methods for each of
|
205
|
+
# the instance variables
|
206
|
+
case data.class.to_s
|
207
|
+
when /^Azure::Resources::Mgmt::.*::Models::GenericResource$/,
|
208
|
+
/^Azure::Resources::Mgmt::.*::Models::ResourceGroup$/
|
209
|
+
# iterate around the instance variables
|
210
|
+
data.instance_variables.each do |var|
|
211
|
+
create_method(object, var.to_s.delete("@"), data.instance_variable_get(var))
|
212
|
+
end
|
213
|
+
# When the data is a Hash object iterate around each of the key value pairs and
|
214
|
+
# craete a method for each one.
|
215
|
+
when "Hash"
|
216
|
+
data.each do |key, value|
|
217
|
+
create_method(object, key, value)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
# Method that is responsible for creating the method on the calling object. This is
|
225
|
+
# because some nesting maybe required. For example of the value is a Hash then it will
|
226
|
+
# need to have an AzureResourceProbe create for each key, whereas if it is a simple
|
227
|
+
# string then the value just needs to be returned
|
228
|
+
#
|
229
|
+
# @private
|
230
|
+
#
|
231
|
+
# @param AzureResourceProbe|AzureResource object Object on which the methods need to be created
|
232
|
+
# @param string name The name of the method
|
233
|
+
# @param variant value The value that needs to be returned by the method
|
234
|
+
def create_method(object, name, value)
|
235
|
+
# Create the necessary method based on the var that has been passed
|
236
|
+
# Test the value for its type so that the method can be setup correctly
|
237
|
+
case value.class.to_s
|
238
|
+
when "String", "Integer", "TrueClass", "FalseClass", "Fixnum"
|
239
|
+
object.define_singleton_method name do
|
240
|
+
value
|
241
|
+
end
|
242
|
+
when "Hash"
|
243
|
+
value.count == 0 ? return_value = value : return_value = AzureResourceProbe.new(value)
|
244
|
+
object.define_singleton_method name do
|
245
|
+
return_value
|
246
|
+
end
|
247
|
+
when /^Azure::Resources::Mgmt::.*::Models::ResourceGroupProperties$/
|
248
|
+
# This is a special case where the properties of the resource group is not a simple JSON model
|
249
|
+
# This is because the plugin is using the Azure SDK to get this information so it is an SDK object
|
250
|
+
# that has to be interrogated in a different way. This is the only object type that behaves like this
|
251
|
+
value.instance_variables.each do |var|
|
252
|
+
create_method(object, var.to_s.delete("@"), value.instance_variable_get(var))
|
253
|
+
end
|
254
|
+
when "Array"
|
255
|
+
# Some things are just string or integer arrays
|
256
|
+
# Check this by seeing if the first element is a string / integer / boolean or
|
257
|
+
# a hashtable
|
258
|
+
# This may not be the best methid, but short of testing all elements in the array, this is
|
259
|
+
# the quickest test
|
260
|
+
case value[0].class.to_s
|
261
|
+
when "String", "Integer", "TrueClass", "FalseClass", "Fixnum"
|
262
|
+
probes = value
|
263
|
+
else
|
264
|
+
probes = []
|
265
|
+
value.each do |value_item|
|
266
|
+
probes << AzureResourceProbe.new(value_item)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
object.define_singleton_method name do
|
270
|
+
probes
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Class object to maintain a count of the Azure Resource types that are found
|
277
|
+
# when a less specific test is carried out. For example if all the resoures of a resource
|
278
|
+
# group are called for, there will be variaous types and number of those types.
|
279
|
+
#
|
280
|
+
# Each type is namespaced, so for example a virtual machine has the type 'Microsoft.Compute/virtualMachines'
|
281
|
+
# This is broken down into the 'Microsoft' class with the type 'Compute/virtualMachines'
|
282
|
+
# This has been done for two reasons:
|
283
|
+
# 1. Enable the dotted notation to work in the test
|
284
|
+
# 2. Allow third party resource types ot be catered for if they are ever enabled by Microsoft
|
285
|
+
#
|
286
|
+
# @author Russell Seymour
|
287
|
+
# @since 0.2.0
|
288
|
+
class AzureResourceTypeCounts
|
289
|
+
# Constructor to setup a new class for a specific Azure Resource type.
|
290
|
+
# It should be passed a hashtable with information such as:
|
291
|
+
# {
|
292
|
+
# "Compute/virtualMachines" => 2,
|
293
|
+
# "Network/networkInterfaces" => 3
|
294
|
+
# }
|
295
|
+
# This will result in two methods being created on the class:
|
296
|
+
# - Compute/virtualNetworks
|
297
|
+
# - Network/networkInterfaces
|
298
|
+
# Each of which will return the corresponding count value
|
299
|
+
#
|
300
|
+
# @param Hash counts Hash table of types and the count of each one
|
301
|
+
#
|
302
|
+
# @return AzureResourceTypeCounts
|
303
|
+
def initialize(counts)
|
304
|
+
counts.each do |type, count|
|
305
|
+
define_singleton_method type do
|
306
|
+
count
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# Class object that is created for each element that is returned by Azure.
|
313
|
+
# This is what is interogated by Inspec. If they are nested hashes, then this results
|
314
|
+
# in nested AzureResourceProbe objects.
|
315
|
+
#
|
316
|
+
# For example, if the following was seen in an Azure Resource
|
317
|
+
# properties -> storageProfile -> imageReference
|
318
|
+
# Would result in the following nestec classes
|
319
|
+
# AzureResource -> AzureResourceProbe -> AzureResourceProbe
|
320
|
+
#
|
321
|
+
# The methods for each of the classes are dynamically defined at run time and will
|
322
|
+
# match the items that are retrieved from Azure. See the 'test/integration/verify/controls' for
|
323
|
+
# examples
|
324
|
+
#
|
325
|
+
# This class will not be called externally
|
326
|
+
#
|
327
|
+
# @author Russell Seymour
|
328
|
+
# @since 0.2.0
|
329
|
+
# @attr_reader string name Name of the Azure resource
|
330
|
+
# @attr_reader string type Type of the Azure Resource
|
331
|
+
# @attr_reader string location Location in Azure of the resource
|
332
|
+
class AzureResourceProbe
|
333
|
+
attr_reader :name, :type, :location, :item, :count
|
334
|
+
|
335
|
+
# Initialize method for the class. Accepts an item, be it a scalar value, hash or Azure object
|
336
|
+
# It will then create the necessary dynamic methods so that they can be called in the tests
|
337
|
+
# This is accomplished by call the AzureResourceDynamicMethods
|
338
|
+
#
|
339
|
+
# @param varaint The item from which the class will be initialized
|
340
|
+
#
|
341
|
+
# @return AzureResourceProbe
|
342
|
+
def initialize(item)
|
343
|
+
dm = AzureResourceDynamicMethods.new
|
344
|
+
dm.create_methods(self, item)
|
345
|
+
|
346
|
+
# Set the item as a property on the class
|
347
|
+
# This is so that it is possible to interrogate what has been added to the class and isolate them from
|
348
|
+
# the standard methods that a Ruby class has.
|
349
|
+
# This used for checking Tags on a resource for example
|
350
|
+
# It also allows direct access if so required
|
351
|
+
@item = item
|
352
|
+
|
353
|
+
# Set how many items have been set
|
354
|
+
@count = item.length
|
355
|
+
end
|
356
|
+
|
357
|
+
# Allows resources to respond to the include test
|
358
|
+
# This means that things like tags can be checked for and then their value tested
|
359
|
+
#
|
360
|
+
# @author Russell Seymour
|
361
|
+
#
|
362
|
+
# @param [String] key Name of the item to look for in the @item property
|
363
|
+
def include?(key)
|
364
|
+
@item.key?(key)
|
365
|
+
end
|
366
|
+
|
367
|
+
# Give a sting like `computer_name` return the camelCase version, e.g.
|
368
|
+
# computerName
|
369
|
+
#
|
370
|
+
# @param string data Data that needs to be converted from snake_case to camelCase
|
371
|
+
#
|
372
|
+
# @return string
|
373
|
+
def camel_case(data)
|
374
|
+
camel_case_data = data.split("_").inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
|
375
|
+
|
376
|
+
# Ensure that gb (as in gigabytes) is uppercased
|
377
|
+
camel_case_data.gsub(/[gb]/, &:upcase)
|
378
|
+
end
|
379
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require "resources/azure/azure_backend"
|
2
|
+
require "inspec/utils/filter"
|
3
|
+
|
4
|
+
module Inspec::Resources
|
5
|
+
class AzureGenericResource < AzureResourceBase
|
6
|
+
name "azure_generic_resource"
|
7
|
+
|
8
|
+
desc '
|
9
|
+
InSpec Resource to interrogate any Resource type in Azure
|
10
|
+
'
|
11
|
+
|
12
|
+
supports platform: "azure"
|
13
|
+
|
14
|
+
attr_accessor :filter, :total, :counts, :name, :type, :location, :probes
|
15
|
+
|
16
|
+
def initialize(opts = {})
|
17
|
+
Inspec.deprecate(:resource_azure_generic_resource)
|
18
|
+
|
19
|
+
# Call the parent class constructor
|
20
|
+
super(opts)
|
21
|
+
|
22
|
+
# Get the resource group
|
23
|
+
resource_group
|
24
|
+
|
25
|
+
# Get the resources
|
26
|
+
resources
|
27
|
+
|
28
|
+
# Create the tag methods
|
29
|
+
create_tag_methods
|
30
|
+
end
|
31
|
+
|
32
|
+
# Define the filter table so that it can be interrogated
|
33
|
+
@filter = FilterTable.create
|
34
|
+
@filter.register_filter_method(:contains)
|
35
|
+
.register_column(:type, field: "type")
|
36
|
+
.register_column(:name, field: "name")
|
37
|
+
.register_column(:location, field: "location")
|
38
|
+
.register_column(:properties, field: "properties")
|
39
|
+
|
40
|
+
@filter.install_filter_methods_on_resource(self, :probes)
|
41
|
+
|
42
|
+
def parse_resource(resource)
|
43
|
+
# return a hash of information
|
44
|
+
parsed = {
|
45
|
+
"location" => resource.location,
|
46
|
+
"name" => resource.name,
|
47
|
+
"type" => resource.type,
|
48
|
+
"exist?" => true,
|
49
|
+
"properties" => AzureResourceProbe.new(resource.properties),
|
50
|
+
}
|
51
|
+
|
52
|
+
parsed
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require "resources/azure/azure_backend"
|
2
|
+
|
3
|
+
module Inspec::Resources
|
4
|
+
class AzureResourceGroup < AzureResourceBase
|
5
|
+
name "azure_resource_group"
|
6
|
+
|
7
|
+
desc '
|
8
|
+
InSpec Resource to get metadata about a specific Resource Group
|
9
|
+
'
|
10
|
+
|
11
|
+
supports platform: "azure"
|
12
|
+
|
13
|
+
attr_reader :name, :location, :id, :total, :counts, :mapping
|
14
|
+
|
15
|
+
# Constructor to get the resource group itself and perform some analysis on the
|
16
|
+
# resources that in the resource group.
|
17
|
+
#
|
18
|
+
# This analysis is defined by the the mapping hashtable which is used to define
|
19
|
+
# the 'has_xxx?' methods (see AzureResourceGroup#create_has_methods) and return
|
20
|
+
# the counts for each type
|
21
|
+
#
|
22
|
+
# @author Russell Seymour
|
23
|
+
def initialize(opts)
|
24
|
+
opts.key?(:name) ? opts[:group_name] = opts[:name] : false
|
25
|
+
# Ensure that the opts only have the name of the resource group set
|
26
|
+
opts.select! { |k, _v| k == :group_name }
|
27
|
+
super(opts)
|
28
|
+
|
29
|
+
# set the mapping for the Azure Resources
|
30
|
+
@mapping = {
|
31
|
+
nic: "Microsoft.Network/networkInterfaces",
|
32
|
+
vm: "Microsoft.Compute/virtualMachines",
|
33
|
+
extension: "Microsoft.Compute/virtualMachines/extensions",
|
34
|
+
nsg: "Microsoft.Network/networkSecurityGroups",
|
35
|
+
vnet: "Microsoft.Network/virtualNetworks",
|
36
|
+
managed_disk: "Microsoft.Compute/disks",
|
37
|
+
managed_disk_image: "Microsoft.Compute/images",
|
38
|
+
sa: "Microsoft.Storage/storageAccounts",
|
39
|
+
public_ip: "Microsoft.Network/publicIPAddresses",
|
40
|
+
}
|
41
|
+
|
42
|
+
# Get information about the resource group itself
|
43
|
+
resource_group
|
44
|
+
|
45
|
+
# Get information about the resources in the resource group
|
46
|
+
resources
|
47
|
+
|
48
|
+
# Call method to create the has_xxxx? methods
|
49
|
+
create_has_methods
|
50
|
+
|
51
|
+
# Call method to allow access to the tag values
|
52
|
+
create_tag_methods
|
53
|
+
end
|
54
|
+
|
55
|
+
# Return the provisioning state of the resource group
|
56
|
+
#
|
57
|
+
# @author Russell Seymour
|
58
|
+
def provisioning_state
|
59
|
+
properties.provisioningState
|
60
|
+
end
|
61
|
+
|
62
|
+
# Analyze the fully qualified id of the resource group to return the subscription id
|
63
|
+
# that this resource group is part of
|
64
|
+
#
|
65
|
+
# The format of the id is
|
66
|
+
# /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>
|
67
|
+
#
|
68
|
+
# @author Russell Seymour
|
69
|
+
def subscription_id
|
70
|
+
id.split(%r{\/}).reject(&:empty?)[1]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Method to parse the resources that have been returned
|
74
|
+
# This allows the calculations of the amount of resources to be determined
|
75
|
+
#
|
76
|
+
# @author Russell Seymour
|
77
|
+
#
|
78
|
+
# @param [Hash] resource A hashtable representing the resource group
|
79
|
+
def parse_resource(resource)
|
80
|
+
# return a hash of information
|
81
|
+
parsed = {
|
82
|
+
"name" => resource.name,
|
83
|
+
"type" => resource.type,
|
84
|
+
}
|
85
|
+
|
86
|
+
parsed
|
87
|
+
end
|
88
|
+
|
89
|
+
# This method catches the xxx_count calls that are made on the resource.
|
90
|
+
#
|
91
|
+
# The method that is called is stripped of '_count' and then compared with the
|
92
|
+
# mappings table. If that type exists then the number of those items is returned.
|
93
|
+
# However if that type is not in the Resource Group then the method will return
|
94
|
+
# a NoMethodError exception
|
95
|
+
#
|
96
|
+
# @author Russell Seymour
|
97
|
+
#
|
98
|
+
# @param [Symbol] method_id The name of the method that was called
|
99
|
+
def method_missing(method_id)
|
100
|
+
# Determine the mapping_key based on the method_id
|
101
|
+
mapping_key = method_id.to_s.chomp("_count").to_sym
|
102
|
+
|
103
|
+
if mapping.key?(mapping_key)
|
104
|
+
# based on the method id get the
|
105
|
+
namespace, type_name = mapping[mapping_key].split(/\./)
|
106
|
+
|
107
|
+
# check that the type_name is defined, if not return 0
|
108
|
+
if send(namespace).methods.include?(type_name.to_sym)
|
109
|
+
# return the count for the method id
|
110
|
+
send(namespace).send(type_name)
|
111
|
+
else
|
112
|
+
0
|
113
|
+
end
|
114
|
+
else
|
115
|
+
msg = format("undefined method `%s` for %s", method_id, self.class)
|
116
|
+
raise NoMethodError, msg
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
# For each of the mappings this method creates the has_xxx? method. This allows the use
|
123
|
+
# of the following type of test
|
124
|
+
#
|
125
|
+
# it { should have_nics }
|
126
|
+
#
|
127
|
+
# For example, it will create a has_nics? method that returns a boolean to state of the
|
128
|
+
# resource group has any nics at all.
|
129
|
+
#
|
130
|
+
# @author Russell Seymour
|
131
|
+
# @private
|
132
|
+
def create_has_methods
|
133
|
+
return if failed_resource?
|
134
|
+
|
135
|
+
# Create the has methods for each of the mappings
|
136
|
+
# This is a quick test to show that the resource group has at least one of these things
|
137
|
+
mapping.each do |name, type|
|
138
|
+
# Determine the name of the method name
|
139
|
+
method_name = format("has_%ss?", name)
|
140
|
+
namespace, type_name = type.split(/\./)
|
141
|
+
|
142
|
+
# use the namespace and the type_name to determine if the resource group has this type or not
|
143
|
+
result = send(namespace).methods.include?(type_name.to_sym) ? true : false
|
144
|
+
|
145
|
+
define_singleton_method method_name do
|
146
|
+
result
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|