openstack_taster 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d197f40b701de60109b9171f250962922dc2f860
4
+ data.tar.gz: 70c9e7da35b886369ae89a4dece5b1e50260235a
5
+ SHA512:
6
+ metadata.gz: d2868e2dae6f66633d73f6ddb54edf80b905e3c35b6b08121128c77729ce597a5e64d7f76c08d1f26e5cb56f9140499e76de3e44ea2e8904b03693e903bbf455
7
+ data.tar.gz: f391b2d1d487bd2e4fedd8ba9aaa49f7599b3769ff71d0c120fdb9f93b9a63426deb8c8da841bee5d302cb1636c9d080f13bdee7e684bf7f70bcdda343744901
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'optparse'
5
+
6
+ suites = {
7
+ 'security' => 'Runs the security test suite',
8
+ 'volumes' => 'Runs the volume test suite'
9
+ }
10
+ settings = {}
11
+
12
+ ARGV << '-h' if ARGV.empty?
13
+
14
+ parser = OptionParser.new do |opts|
15
+ opts.banner = "Usage: openstack_taster <image_name> {suite_name} [--create-snapshot]\n" \
16
+ " or: openstack_taster <image_name> [--create-snapshot]\n" \
17
+ ' or: openstack_taster <option>'
18
+ opts.separator('')
19
+ opts.separator('Available Arguments:')
20
+ opts.on('-c', '--create-snapshot', 'Create snapshot upon test failure.') do
21
+ settings[:create_snapshot] = true
22
+ end
23
+ opts.on('-h', '--help', 'Print usage information.') do
24
+ puts opts
25
+ settings[:exit] = true
26
+ end
27
+ opts.separator('')
28
+ opts.separator('Test Suites:')
29
+ suites.each do |suite, desc|
30
+ opts.separator(" #{suite}\t\t#{desc}")
31
+ end
32
+ end
33
+
34
+ begin
35
+ params = parser.parse!
36
+ rescue OptionParser::InvalidOption => io
37
+ puts io.message
38
+ puts
39
+ puts parser
40
+ exit 1
41
+ rescue StandardError => e
42
+ puts 'Argument parsing failed:'
43
+ puts e.message
44
+ puts e.backtrace
45
+ exit 1
46
+ end
47
+
48
+ exit if settings[:exit] # exit inside OptionParser causes problems.
49
+
50
+ begin
51
+ case params.length
52
+ when 1
53
+ image_name = params[0]
54
+ suites.each_key { |suite| settings[suite.to_sym] = true }
55
+ when 2
56
+ raise "#{params[1]} is not a test suite!" unless suites.include? params[1]
57
+ image_name = params[0]
58
+ settings[params[1].to_sym] = true
59
+ else
60
+ raise 'Incorrect format!'
61
+ end
62
+ rescue StandardError => e
63
+ puts e.message
64
+ puts
65
+ puts parser
66
+ exit(1)
67
+ end
68
+
69
+ require 'fog/openstack'
70
+ require 'openstack_taster'
71
+
72
+ auth_url = String.new(ENV['OS_AUTH_URL'])
73
+ auth_url << '/tokens' unless auth_url.end_with?('tokens')
74
+ auth_url.freeze
75
+
76
+ OPENSTACK_CREDS = {
77
+ openstack_auth_url: auth_url,
78
+ openstack_username: ENV['OS_USERNAME'],
79
+ openstack_tenant: ENV['OS_TENANT_NAME'],
80
+ openstack_api_key: ENV['OS_PASSWORD']
81
+ }.freeze
82
+
83
+ SSH_KEYS = {
84
+ keypair: ENV['OS_SSH_KEYPAIR'],
85
+ private_key: ENV['OS_PRIVATE_SSH_KEY'],
86
+ public_key: ENV['OS_PUBLIC_SSH_KEY'] # REVIEW
87
+ }.freeze
88
+
89
+ controller_host = auth_url.split(':')[1].delete('//')
90
+ LOG_DIR = "logs/#{controller_host}"
91
+
92
+ compute = Fog::Compute::OpenStack.new(OPENSTACK_CREDS)
93
+ volume = Fog::Volume::OpenStack.new(OPENSTACK_CREDS)
94
+ image = Fog::Image::OpenStack.new(OPENSTACK_CREDS)
95
+ network = Fog::Network::OpenStack.new(OPENSTACK_CREDS)
96
+
97
+ exit OpenStackTaster.new(
98
+ compute, volume, image, network,
99
+ SSH_KEYS, LOG_DIR
100
+ ).taste(image_name, settings)
@@ -0,0 +1,431 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'date'
5
+ require 'excon'
6
+ require 'net/ssh'
7
+ require 'pry'
8
+ require 'inspec'
9
+
10
+ # @author Andrew Tolvstad, Samarendra Hedaoo, Cody Holliday
11
+ class OpenStackTaster
12
+ INSTANCE_FLAVOR_NAME = 'm1.small'
13
+ INSTANCE_NETWORK_NAME = 'public'
14
+ INSTANCE_NAME_PREFIX = 'taster'
15
+ INSTANCE_VOLUME_MOUNT_POINT = '/mnt/taster_volume'
16
+
17
+ VOLUME_TEST_FILE_NAME = 'info'
18
+ VOLUME_TEST_FILE_CONTENTS = nil # Contents would be something like 'test-vol-1 on openpower8.osuosl.bak'
19
+ TIMEOUT_INSTANCE_CREATE = 20
20
+ TIMEOUT_VOLUME_ATTACH = 10
21
+ TIMEOUT_VOLUME_PERSIST = 20
22
+ TIMEOUT_INSTANCE_TO_BE_CREATED = 20
23
+ TIMEOUT_INSTANCE_STARTUP = 30
24
+ TIMEOUT_SSH_RETRY = 15
25
+
26
+ MAX_SSH_RETRY = 3
27
+
28
+ TIME_SLUG_FORMAT = '%Y%m%d_%H%M%S'
29
+
30
+ def initialize(
31
+ compute_service,
32
+ volume_service,
33
+ image_service,
34
+ network_service,
35
+ ssh_keys,
36
+ log_dir
37
+ )
38
+ @compute_service = compute_service
39
+ @volume_service = volume_service
40
+ @image_service = image_service
41
+ @network_service = network_service
42
+
43
+ @volumes = @volume_service.volumes
44
+
45
+ @ssh_keypair = ssh_keys[:keypair]
46
+ @ssh_private_key = ssh_keys[:private_key]
47
+ @ssh_public_key = ssh_keys[:public_key] # REVIEW
48
+
49
+ @session_id = object_id
50
+ @log_dir = log_dir + "/#{@session_id}"
51
+
52
+ @instance_flavor = @compute_service.flavors
53
+ .select { |flavor| flavor.name == INSTANCE_FLAVOR_NAME }.first
54
+ @instance_network = @network_service.networks
55
+ .select { |network| network.name == INSTANCE_NETWORK_NAME }.first
56
+ end
57
+
58
+ # Taste a specified image
59
+ # @param image_name [String] The name on OpenStack of the image to be tested.
60
+ # @param settings [Hash] A hash of settings to enable and disable tests, snapshot creation upon failure.
61
+ # @return [Boolean] success or failure of tests on image.
62
+ # @note The testing section could be further streamlined by:
63
+ # creating a naming standard for test functions (i.e. taste_<name>)
64
+ # limiting the parameters of each test to be: instance, distro_username
65
+ # Adding a 'suites' subhash to the settings hash
66
+ # Then that subhash can be iterated over, use eval to call each function,
67
+ # appending the suite name to 'taste_' for the function name
68
+ # and passing the standardized parameters
69
+ # @todo Reduce Percieved and Cyclomatic complexity
70
+ # @todo Images over compute service is deprecated
71
+ def taste(image_name, settings)
72
+ image = @compute_service.images
73
+ .select { |i| i.name == image_name }.first
74
+
75
+ abort("#{image_name} is not an available image.") if image.nil?
76
+
77
+ distro_user_name = image.name.downcase.gsub(/[^a-z].*$/, '') # truncate downcased name at first non-alpha char
78
+ distro_arch = image.name.downcase.slice(-2, 2)
79
+ instance_name = format(
80
+ '%s-%s-%s-%s',
81
+ INSTANCE_NAME_PREFIX,
82
+ Time.new.strftime(TIME_SLUG_FORMAT),
83
+ distro_user_name,
84
+ distro_arch
85
+ )
86
+
87
+ FileUtils.mkdir_p(@log_dir) unless Dir.exist?(@log_dir)
88
+
89
+ instance_logger = Logger.new("#{@log_dir}/#{instance_name}.log")
90
+
91
+ error_log(
92
+ instance_logger,
93
+ 'info',
94
+ "Tasting #{image.name} as '#{instance_name}' with username '#{distro_user_name}'.\nBuilding...",
95
+ true
96
+ )
97
+
98
+ instance = @compute_service.servers.create(
99
+ name: instance_name,
100
+ flavor_ref: @instance_flavor.id,
101
+ image_ref: image.id,
102
+ nics: [{ net_id: @instance_network.id }],
103
+ key_name: @ssh_keypair
104
+ )
105
+
106
+ if instance.nil?
107
+ error_log(instance_logger, 'error', 'Failed to create instance.', true)
108
+ return false
109
+ end
110
+
111
+ instance.class.send(:attr_accessor, 'logger')
112
+
113
+ instance.logger = instance_logger
114
+
115
+ instance.wait_for(TIMEOUT_INSTANCE_TO_BE_CREATED) { ready? }
116
+
117
+ error_log(instance.logger, 'info', "Sleeping #{TIMEOUT_INSTANCE_STARTUP} seconds for OS startup...", true)
118
+ sleep TIMEOUT_INSTANCE_STARTUP
119
+
120
+ error_log(instance.logger, 'info', "Testing for instance '#{instance.id}'.", true)
121
+
122
+ # Run tests
123
+ return_values = []
124
+ return_values.push taste_security(instance, distro_user_name) if settings[:security]
125
+ return_values.push taste_volumes(instance, distro_user_name) if settings[:volumes]
126
+
127
+ if settings[:create_snapshot] && !return_values.all?
128
+ error_log(instance.logger, 'info', "Tests failed for instance '#{instance.id}'. Creating image...", true)
129
+ create_image(instance) # Create image here since it is destroyed before scope returns to taste function
130
+ end
131
+ return return_values.all?
132
+ rescue Fog::Errors::TimeoutError
133
+ puts 'Instance creation timed out.'
134
+ error_log(instance.logger, 'error', "Instance fault: #{instance.fault}")
135
+ return false
136
+ rescue Interrupt
137
+ puts "\nCaught interrupt"
138
+ puts "Exiting session #{@session_id}"
139
+ raise
140
+ ensure
141
+ if instance
142
+ puts "Destroying instance for session #{@session_id}.\n\n"
143
+ instance.destroy
144
+ end
145
+ end
146
+
147
+ # Runs the security test suite using inspec
148
+ # @param instance [Fog::Image::OpenStack::Image] The instance to test.
149
+ # @param username [String] The username to use when logging into the instance.
150
+ # @return [Boolean] Whether or not the image passed hte security tests.
151
+ # @todo Don't crash when connection refused.
152
+ def taste_security(instance, username)
153
+ opts = {
154
+ 'backend' => 'ssh',
155
+ 'host' => instance.addresses['public'].first['addr'],
156
+ 'port' => 22,
157
+ 'user' => username,
158
+ 'keys_only' => true,
159
+ 'key_files' => @ssh_private_key,
160
+ 'logger' => instance.logger
161
+ }
162
+
163
+ tries = 0
164
+
165
+ begin
166
+ runner = Inspec::Runner.new(opts)
167
+ runner.add_target(File.dirname(__FILE__) + '/../tests')
168
+ runner.run
169
+ rescue RuntimeError => e
170
+ puts "Encountered error \"#{e.message}\" while testing the instance."
171
+ if tries < MAX_SSH_RETRY
172
+ tries += 1
173
+ puts "Initiating SSH attempt #{tries} in #{TIMEOUT_SSH_RETRY} seconds"
174
+ sleep TIMEOUT_SSH_RETRY
175
+ retry
176
+ end
177
+ error_log(instance.logger, 'error', e.backtrace, false, 'Inspec Runner')
178
+ error_log(instance.logger, 'error', e.message, false, 'Inspec Runner')
179
+ return true
180
+ rescue StandardError => e
181
+ puts "Encountered error \"#{e.message}\". Aborting test."
182
+ return true
183
+ end
184
+
185
+ error_log(
186
+ instance.logger,
187
+ 'info',
188
+ "Inspec Test Results\n" +
189
+ runner.report[:controls].map do |test|
190
+ "#{test[:status].upcase}: #{test[:code_desc]}\n#{test[:message]}"
191
+ end.join("\n")
192
+ )
193
+
194
+ if runner.report[:controls].any? { |test| test[:status] == 'failed' }
195
+ error_log(instance.logger, 'warn', 'Image failed security test suite')
196
+ return false
197
+ end
198
+ true
199
+ end
200
+
201
+ # Write an error message to the log and optionally stdout.
202
+ # @param logger [Logger] the logger used to record the message.
203
+ # @param level [String] the level to use when logging.
204
+ # @param message [String] the message to write
205
+ # @param dup_stdout [Boolean] whether or not to print the message to stdout
206
+ # @param context [String] the context of the message to be logged. i.e. SSH, Inspec, etc.
207
+ def error_log(logger, level, message, dup_stdout = false, context = nil)
208
+ puts message if dup_stdout
209
+
210
+ begin
211
+ logger.add(Logger.const_get(level.upcase), message, context)
212
+ rescue NameError
213
+ puts
214
+ puts "\e[31m#{level} is not a severity. Make sure that you use the correct string for logging severity!\e[0m"
215
+ puts
216
+ logger.error('Taster Source Code') { "#{level} is not a logging severity name. Defaulting to INFO." }
217
+ logger.info(context) { message }
218
+ end
219
+ end
220
+
221
+ # Get the name of the image from which an instance was created.
222
+ # @param instance [Fog::Compute::OpenStack::Server] the instance to query
223
+ # @return [String] the name of the image
224
+ def get_image_name(instance)
225
+ @image_service
226
+ .get_image_by_id(instance.image['id'])
227
+ .body['name']
228
+ end
229
+
230
+ # Create an image of an instance.
231
+ # @note This method blocks until snapshot creation is complete on the server.
232
+ # @param instance [Fog::Compute::OpenStack::Server] the instance to query
233
+ # @return [Fog::Image::OpenStack::Image] the generated image
234
+ def create_image(instance)
235
+ image_name = [
236
+ instance.name,
237
+ get_image_name(instance)
238
+ ].join('_')
239
+
240
+ response = instance.create_image(image_name)
241
+ image_id = response.body['image']['id']
242
+
243
+ @image_service.images
244
+ .find_by_id(image_id)
245
+ .wait_for { status == 'active' }
246
+ end
247
+
248
+ # Run the set of tests for each available volume on an instance.
249
+ # @param instance [Fog::Compute::OpenStack::Server] the instance to query
250
+ # @param username [String] the username to use when logging into the instance
251
+ # @return [Boolean] Whether or not the tests succeeded
252
+ def taste_volumes(instance, username)
253
+ mount_failures = @volumes.reject do |volume|
254
+ if volume.attachments.any?
255
+ error_log(instance.logger, 'info', "Volume '#{volume.name}' is already in an attached state; skipping.", true)
256
+ next
257
+ end
258
+
259
+ unless volume_attach?(instance, volume)
260
+ error_log(instance.logger, 'error', "Volume '#{volume.name}' failed to attach.", true)
261
+ next
262
+ end
263
+
264
+ volume_mount_unmount?(instance, username, volume)
265
+ end
266
+
267
+ detach_failures = @volumes.reject do |volume|
268
+ volume_detach?(instance, volume)
269
+ end
270
+
271
+ if mount_failures.empty? && detach_failures.empty?
272
+ error_log(instance.logger, 'info', "\nEncountered 0 failures.", true)
273
+ true
274
+ else
275
+ error_log(
276
+ instance.logger,
277
+ 'error',
278
+ "\nEncountered #{mount_failures.count} mount failures and #{detach_failures.count} detach failures.",
279
+ true
280
+ )
281
+ error_log(instance.logger, 'error', "\nEncountered failures.", true)
282
+ false
283
+ end
284
+ end
285
+
286
+ # A helper method to execute a series of commands remotely on an instance. This helper
287
+ # passes its block directly to `Net::SSH#start()`.
288
+ # @param instance [Fog::Compute::OpenStack::Server] the instance on which to run the commands
289
+ # @param username [String] the username to use when logging into the instance
290
+ # @todo Don't crash when connection refused.
291
+ def with_ssh(instance, username, &block)
292
+ tries = 0
293
+ instance.logger.progname = 'SSH'
294
+ begin
295
+ Net::SSH.start(
296
+ instance.addresses['public'].first['addr'],
297
+ username,
298
+ verbose: :info,
299
+ paranoid: false,
300
+ logger: instance.logger,
301
+ keys: [@ssh_private_key],
302
+ &block
303
+ )
304
+ rescue Errno::ECONNREFUSED => e
305
+ puts "Encountered #{e.message} while connecting to the instance."
306
+ if tries < MAX_SSH_RETRY
307
+ tries += 1
308
+ puts "Initiating SSH attempt #{tries} in #{TIMEOUT_SSH_RETRY} seconds"
309
+ sleep TIMEOUT_SSH_RETRY
310
+ retry
311
+ end
312
+ error_log(instance.logger, 'error', e.backtrace, false, 'SSH')
313
+ error_log(instance.logger, 'error', e.message, false, 'SSH')
314
+ exit 1
315
+ end
316
+ end
317
+
318
+ # Test volume attachment for a given instance and volume.
319
+ # @param instance [Fog::Compute::OpenStack::Server] the instance to which to attach the volume
320
+ # @param volume [Fog::Volume::OpenStack::Volume] the volume to attach
321
+ # @return [Boolean] whether or not the attachment was successful
322
+ def volume_attach?(instance, volume)
323
+ volume_attached = lambda do |_|
324
+ volume_attachments.any? do |attachment|
325
+ attachment['volumeId'] == volume.id
326
+ end
327
+ end
328
+
329
+ error_log(instance.logger, 'info', "Attaching volume '#{volume.name}' (#{volume.id})...", true)
330
+ @compute_service.attach_volume(volume.id, instance.id, nil)
331
+ instance.wait_for(TIMEOUT_VOLUME_ATTACH, &volume_attached)
332
+
333
+ error_log(instance.logger, 'info', "Sleeping #{TIMEOUT_VOLUME_PERSIST} seconds for attachment persistance...", true)
334
+ sleep TIMEOUT_VOLUME_PERSIST
335
+
336
+ # In the off chance that the volume host goes down, catch it.
337
+ if instance.instance_eval(&volume_attached)
338
+ return true if volume.reload.attachments.first
339
+ error_log(instance.logger, 'error', "Failed to attach '#{volume.name}': Volume host might be down.", true)
340
+ else
341
+ error_log(instance.logger, 'error', "Failed to attach '#{volume.name}': Volume was unexpectedly detached.", true)
342
+ end
343
+
344
+ false
345
+ rescue Excon::Error => e
346
+ puts 'Error attaching volume, check log for details.'
347
+ error_log(instance.logger, 'error', e.message)
348
+ false
349
+ rescue Fog::Errors::TimeoutError
350
+ error_log(instance.logger, 'error', "Failed to attach '#{volume.name}': Operation timed out.", true)
351
+ false
352
+ end
353
+
354
+ # Test volume mounting and unmounting for an instance and a volume.
355
+ # @param instance [Fog::Compute::OpenStack::Server] the instance on which to mount the volume
356
+ # @param username [String] the username to use when logging into the instance
357
+ # @param volume [Fog::Volume::OpenStack::Volume] the volume to mount
358
+ # @return [Boolean] whether or not the mounting/unmounting was successful
359
+ def volume_mount_unmount?(instance, username, volume)
360
+ mount = INSTANCE_VOLUME_MOUNT_POINT
361
+ file_name = VOLUME_TEST_FILE_NAME
362
+ file_contents = VOLUME_TEST_FILE_CONTENTS
363
+ vdev = @volume_service.volumes.find_by_id(volume.id)
364
+ .attachments.first['device']
365
+ vdev << '1'
366
+
367
+ log_partitions(instance, username)
368
+
369
+ commands = [
370
+ ["echo -e \"127.0.0.1\t$HOSTNAME\" | sudo tee -a /etc/hosts", nil], # to fix problems with sudo and DNS resolution
371
+ ['sudo partprobe -s', nil],
372
+ ["[ -d '#{mount}' ] || sudo mkdir #{mount}", ''],
373
+ ["sudo mount #{vdev} #{mount}", ''],
374
+ ["sudo cat #{mount}/#{file_name}", file_contents],
375
+ ["sudo umount #{mount}", '']
376
+ ]
377
+
378
+ error_log(instance.logger, 'info', "Mounting volume '#{volume.name}' (#{volume.id})...", true)
379
+
380
+ error_log(instance.logger, 'info', 'Mounting from inside the instance...', true)
381
+ with_ssh(instance, username) do |ssh|
382
+ commands.each do |command, expected|
383
+ result = ssh.exec!(command).chomp
384
+ if expected.nil?
385
+ error_log(instance.logger, 'info', "#{command} yielded '#{result}'")
386
+ elsif result != expected
387
+ error_log(
388
+ instance.logger,
389
+ 'error',
390
+ "Failure while running '#{command}':\n\texpected '#{expected}'\n\tgot '#{result}'",
391
+ true
392
+ )
393
+ return false # returns from volume_mount_unmount?
394
+ end
395
+ end
396
+ end
397
+ true
398
+ end
399
+
400
+ # Log instance's partition listing.
401
+ # @param instance [Fog::Compute::OpenStack::Server] the instance to log
402
+ # @param username [String] the username to use when logging in to the instance
403
+ def log_partitions(instance, username)
404
+ puts 'Logging partition list and dmesg...'
405
+
406
+ record_info_commands = [
407
+ 'cat /proc/partitions',
408
+ 'dmesg | tail -n 20'
409
+ ]
410
+
411
+ with_ssh(instance, username) do |ssh|
412
+ record_info_commands.each do |command|
413
+ result = ssh.exec!(command)
414
+ error_log(instance.logger, 'info', "Ran '#{command}' and got '#{result}'")
415
+ end
416
+ end
417
+ end
418
+
419
+ # Detach volume from instance.
420
+ # @param instance [Fog::Compute::OpenStack::Server] the instance from which to detach
421
+ # @param volume [Fog::Volume::OpenStack::Volume] the volume to detach
422
+ # @return [Boolean] whether or not the detachment succeeded
423
+ def volume_detach?(instance, volume)
424
+ error_log(instance.logger, 'info', "Detaching #{volume.name}.", true)
425
+ instance.detach_volume(volume.id)
426
+ rescue Excon::Error => e
427
+ puts 'Failed to detach. check log for details.'
428
+ error_log(instance.logger, 'error', e.message)
429
+ false
430
+ end
431
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+ control 'security-1.0' do
3
+ impact 1.0
4
+ title 'Openstack Image Security Test'
5
+ desc 'Tests the security of images used for Openstack.'
6
+
7
+ username = user.username
8
+
9
+ describe sshd_config do
10
+ its('PermitRootLogin') { should eq 'no' }
11
+ its('PasswordAuthentication') { should eq 'no' }
12
+ its('ChallengeResponseAuthentication') { should eq 'no' }
13
+ its('KbdInteractiveAuthentication') { should eq 'no' }
14
+ end
15
+
16
+ describe 'running sshd config' do
17
+ let(:resource) { command('sudo sshd -T') }
18
+
19
+ it 'should not permit root login' do
20
+ expect(resource.stdout).to cmp(/^PermitRootLogin no/i)
21
+ end
22
+
23
+ it 'should not permit password authentication' do
24
+ expect(resource.stdout).to cmp(/^PasswordAuthentication no/i)
25
+ end
26
+
27
+ it 'should not permit challenge response authentication' do
28
+ expect(resource.stdout).to cmp(/^ChallengeResponseAuthentication no/i)
29
+ end
30
+ it 'should not permit keyboard interactive authentication' do
31
+ expect(resource.stdout).to cmp(/^KbdInteractiveAuthentication no/i)
32
+ end
33
+ end
34
+
35
+ # Our version of inspec does not give us a warning about the list matcher,
36
+ # but in version 2.0 of inspec this will be removed.
37
+ # This tests the number of instances of sshd on the system.
38
+ describe processes('sshd') do
39
+ its('list.length') { should eq 1 }
40
+ end
41
+
42
+ describe.one do
43
+ describe user(username) do
44
+ its('groups') { should eq %w(root wheel sudo) }
45
+ end
46
+
47
+ describe command('sudo -U ' + username + ' -l') do
48
+ its('stdout') { should cmp(/\(ALL\) ((NO)*PASSWD)*: ALL/) }
49
+ end
50
+ end
51
+ end
data/tests/inspec.yml ADDED
@@ -0,0 +1,7 @@
1
+ name: openpower_security
2
+ title: OpenPower Security Test Suite
3
+ maintainer: OSU Open Source Lab
4
+ copyright: Oregon State University
5
+ license: Apache License, Version 2.0
6
+ summary: Verify that an image has correctly configured security settings.
7
+ version: 1.0.0
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: openstack_taster
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - OSU Open Source Lab
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-07-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: inspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.10'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.10.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.10'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.10.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: fog-openstack
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.1.19
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 0.1.19
47
+ - !ruby/object:Gem::Dependency
48
+ name: net-ssh
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.2'
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 3.2.0
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '3.2'
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 3.2.0
67
+ - !ruby/object:Gem::Dependency
68
+ name: json
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '1.8'
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 1.8.6
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '1.8'
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: 1.8.6
87
+ description: Tastes images on an OpenStack deployment for security and basic usability.
88
+ email: support@osuosl.org
89
+ executables:
90
+ - openstack_taster
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - bin/openstack_taster
95
+ - lib/openstack_taster.rb
96
+ - tests/controls/security_test.rb
97
+ - tests/inspec.yml
98
+ homepage: https://github.com/osuosl/openstack_taster
99
+ licenses:
100
+ - Apache-2.0
101
+ metadata: {}
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 2.6.10
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Taste all of the OpenStack's basic functionality for an image
122
+ test_files: []