openstack_taster 1.0.2 → 1.1.0
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 +4 -4
- data/bin/openstack_taster +31 -14
- data/lib/openstack_taster.rb +69 -32
- data/tests/controls/security_test.rb +9 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f23a80c511e0cdf522d55d4e398b7f8ba95d9d5
|
4
|
+
data.tar.gz: 4337f06484caba1510f9e0fa6b46ce1708c7e107
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 970215a90be5debc307211a6733e02a016949ea7fd2165bcef7ae4c9b3d5a6a6ce9879abd9ff4100d15a2042682ac8995b08924306e73b5fb01040cd3474437d
|
7
|
+
data.tar.gz: da521ffbf76233edca3d11d94ff634ea99b1a4306aa9942b3a2288d11bd15e168e4df432ad15de6636ec8e151d7d9332175ff521ed5890ce4898d0ea0fee3a1a
|
data/bin/openstack_taster
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'optparse'
|
5
|
+
require 'English'
|
5
6
|
|
6
7
|
suites = {
|
7
8
|
'security' => 'Runs the security test suite',
|
@@ -12,11 +13,21 @@ settings = {}
|
|
12
13
|
ARGV << '-h' if ARGV.empty?
|
13
14
|
|
14
15
|
parser = OptionParser.new do |opts|
|
15
|
-
opts.banner = "Usage: openstack_taster <image_name>
|
16
|
-
" or: openstack_taster <image_name> [--create-snapshot]\n" \
|
17
|
-
' or: openstack_taster <option>'
|
16
|
+
opts.banner = "Usage: openstack_taster -i <image_name> -u <ssh_user> [-s suite_name] [--create-snapshot]\n"
|
18
17
|
opts.separator('')
|
19
18
|
opts.separator('Available Arguments:')
|
19
|
+
opts.on('-i', '--image=<image_name>', 'Image name to use for the instance.') do |i|
|
20
|
+
settings[:image_name] = i
|
21
|
+
end
|
22
|
+
opts.on('-u', '--user=<ssh_user>', 'SSH username for logging into the instance.') do |u|
|
23
|
+
settings[:ssh_user] = u
|
24
|
+
end
|
25
|
+
opts.on('-s', '--suite=<suite_name>', 'Test suite(s) to use.') do |s|
|
26
|
+
settings[:suite] = s
|
27
|
+
end
|
28
|
+
opts.on('-f', '--flavor=<flavor>', 'Set flavor for the instance.') do |f|
|
29
|
+
settings[:flavor] = f
|
30
|
+
end
|
20
31
|
opts.on('-c', '--create-snapshot', 'Create snapshot upon test failure.') do
|
21
32
|
settings[:create_snapshot] = true
|
22
33
|
end
|
@@ -32,7 +43,16 @@ parser = OptionParser.new do |opts|
|
|
32
43
|
end
|
33
44
|
|
34
45
|
begin
|
35
|
-
|
46
|
+
parser.parse!
|
47
|
+
mandatory = [:image_name, :ssh_user]
|
48
|
+
missing = mandatory.select { |p| settings[p].nil? }
|
49
|
+
raise OptionParser::MissingArgument, missing.join(', ') unless missing.empty? || settings[:exit] == true
|
50
|
+
rescue OptionParser::MissingArgument
|
51
|
+
puts
|
52
|
+
puts 'Error: ' + $ERROR_INFO.to_s
|
53
|
+
puts
|
54
|
+
puts parser unless settings[:exit] == true
|
55
|
+
exit 1
|
36
56
|
rescue OptionParser::InvalidOption => io
|
37
57
|
puts io.message
|
38
58
|
puts
|
@@ -48,16 +68,11 @@ end
|
|
48
68
|
exit if settings[:exit] # exit inside OptionParser causes problems.
|
49
69
|
|
50
70
|
begin
|
51
|
-
|
52
|
-
when 1
|
53
|
-
image_name = params[0]
|
71
|
+
if settings[:suite].nil?
|
54
72
|
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
73
|
else
|
60
|
-
raise
|
74
|
+
raise "#{settings[:suite]} is not a test suite!" unless suites.include? settings[:suite]
|
75
|
+
settings[settings[:suite].to_sym] = true
|
61
76
|
end
|
62
77
|
rescue StandardError => e
|
63
78
|
puts e.message
|
@@ -66,6 +81,8 @@ rescue StandardError => e
|
|
66
81
|
exit(1)
|
67
82
|
end
|
68
83
|
|
84
|
+
settings[:flavor] = 'm1.tiny' if settings[:flavor].nil?
|
85
|
+
|
69
86
|
require 'fog/openstack'
|
70
87
|
require 'openstack_taster'
|
71
88
|
|
@@ -95,6 +112,6 @@ image = Fog::Image::OpenStack.new(OPENSTACK_CREDS)
|
|
95
112
|
network = Fog::Network::OpenStack.new(OPENSTACK_CREDS)
|
96
113
|
|
97
114
|
exit OpenStackTaster.new(
|
98
|
-
compute, volume, image, network,
|
115
|
+
compute, volume, image, network, ENV['OS_NETWORK_REF'], settings[:flavor],
|
99
116
|
SSH_KEYS, LOG_DIR
|
100
|
-
).taste(image_name, settings)
|
117
|
+
).taste(settings[:image_name], settings)
|
data/lib/openstack_taster.rb
CHANGED
@@ -9,13 +9,15 @@ require 'inspec'
|
|
9
9
|
|
10
10
|
# @author Andrew Tolvstad, Samarendra Hedaoo, Cody Holliday
|
11
11
|
class OpenStackTaster
|
12
|
-
INSTANCE_FLAVOR_NAME = 'm1.tiny'
|
13
|
-
INSTANCE_NETWORK_NAME = 'public'
|
14
12
|
INSTANCE_NAME_PREFIX = 'taster'
|
15
13
|
INSTANCE_VOLUME_MOUNT_POINT = '/mnt/taster_volume'
|
16
14
|
|
15
|
+
VOLUME_NAME_PREFIX = 'test_volume'
|
16
|
+
VOLUME_DESCRIPTION = 'Test volume for OpenStack Taster.'
|
17
|
+
VOLUME_SIZE = 1
|
18
|
+
VOLUME_FILESYSTEM = 'ext4'
|
17
19
|
VOLUME_TEST_FILE_NAME = 'info'
|
18
|
-
VOLUME_TEST_FILE_CONTENTS =
|
20
|
+
VOLUME_TEST_FILE_CONTENTS = 'test-volume'
|
19
21
|
TIMEOUT_INSTANCE_CREATE = 20
|
20
22
|
TIMEOUT_VOLUME_ATTACH = 10
|
21
23
|
TIMEOUT_VOLUME_PERSIST = 20
|
@@ -32,6 +34,8 @@ class OpenStackTaster
|
|
32
34
|
volume_service,
|
33
35
|
image_service,
|
34
36
|
network_service,
|
37
|
+
network_name,
|
38
|
+
instance_flavor,
|
35
39
|
ssh_keys,
|
36
40
|
log_dir
|
37
41
|
)
|
@@ -40,6 +44,7 @@ class OpenStackTaster
|
|
40
44
|
@image_service = image_service
|
41
45
|
@network_service = network_service
|
42
46
|
|
47
|
+
@network_name = network_name || 'public'
|
43
48
|
@volumes = @volume_service.volumes
|
44
49
|
|
45
50
|
@ssh_keypair = ssh_keys[:keypair]
|
@@ -50,9 +55,9 @@ class OpenStackTaster
|
|
50
55
|
@log_dir = log_dir + "/#{@session_id}"
|
51
56
|
|
52
57
|
@instance_flavor = @compute_service.flavors
|
53
|
-
.select { |flavor|
|
58
|
+
.select { |flavor| flavor.name == instance_flavor }.first
|
54
59
|
@instance_network = @network_service.networks
|
55
|
-
.select { |network| network.name ==
|
60
|
+
.select { |network| network.name == @network_name }.first
|
56
61
|
end
|
57
62
|
|
58
63
|
# Taste a specified image
|
@@ -74,14 +79,12 @@ class OpenStackTaster
|
|
74
79
|
|
75
80
|
abort("#{image_name} is not an available image.") if image.nil?
|
76
81
|
|
77
|
-
|
78
|
-
distro_arch = image.name.downcase.slice(-2, 2)
|
82
|
+
distro = image.name.downcase[/^[a-z]*/]
|
79
83
|
instance_name = format(
|
80
|
-
'%s-%s-%s
|
84
|
+
'%s-%s-%s',
|
81
85
|
INSTANCE_NAME_PREFIX,
|
82
86
|
Time.new.strftime(TIME_SLUG_FORMAT),
|
83
|
-
|
84
|
-
distro_arch
|
87
|
+
distro
|
85
88
|
)
|
86
89
|
|
87
90
|
FileUtils.mkdir_p(@log_dir) unless Dir.exist?(@log_dir)
|
@@ -91,7 +94,8 @@ class OpenStackTaster
|
|
91
94
|
error_log(
|
92
95
|
instance_logger,
|
93
96
|
'info',
|
94
|
-
"Tasting #{image.name} as '#{instance_name}' with username '#{
|
97
|
+
"Tasting #{image.name} as '#{instance_name}' with username '#{settings[:ssh_user]}' and " \
|
98
|
+
"flavor '#{settings[:flavor]}'.\nBuilding...",
|
95
99
|
true
|
96
100
|
)
|
97
101
|
|
@@ -121,8 +125,8 @@ class OpenStackTaster
|
|
121
125
|
|
122
126
|
# Run tests
|
123
127
|
return_values = []
|
124
|
-
return_values.push taste_security(instance,
|
125
|
-
return_values.push taste_volumes(instance,
|
128
|
+
return_values.push taste_security(instance, settings[:ssh_user]) if settings[:security]
|
129
|
+
return_values.push taste_volumes(instance, settings[:ssh_user]) if settings[:volumes]
|
126
130
|
|
127
131
|
if settings[:create_snapshot] && !return_values.all?
|
128
132
|
error_log(instance.logger, 'info', "Tests failed for instance '#{instance.id}'. Creating image...", true)
|
@@ -152,7 +156,7 @@ class OpenStackTaster
|
|
152
156
|
def taste_security(instance, username)
|
153
157
|
opts = {
|
154
158
|
'backend' => 'ssh',
|
155
|
-
'host' => instance.addresses[
|
159
|
+
'host' => instance.addresses[@network_name].first['addr'],
|
156
160
|
'port' => 22,
|
157
161
|
'user' => username,
|
158
162
|
'sudo' => true,
|
@@ -251,37 +255,68 @@ class OpenStackTaster
|
|
251
255
|
# @param username [String] the username to use when logging into the instance
|
252
256
|
# @return [Boolean] Whether or not the tests succeeded
|
253
257
|
def taste_volumes(instance, username)
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
258
|
+
volume_name = format(
|
259
|
+
'%s-%s-%s',
|
260
|
+
VOLUME_NAME_PREFIX,
|
261
|
+
Time.new.strftime(TIME_SLUG_FORMAT),
|
262
|
+
instance.id
|
263
|
+
)
|
264
|
+
begin
|
265
|
+
volume = @compute_service.volumes.create name: volume_name, size: VOLUME_SIZE, description: VOLUME_DESCRIPTION
|
266
|
+
rescue Excon::Error => e
|
267
|
+
puts 'Failed to create volume. check log for details.'
|
268
|
+
error_log(instance.logger, 'error', e.message)
|
269
|
+
false
|
270
|
+
end
|
264
271
|
|
265
|
-
|
272
|
+
loop do
|
273
|
+
volume.reload
|
274
|
+
sleep 2
|
275
|
+
break if volume.ready?
|
276
|
+
error_log(instance.logger, 'info', "volume #{volume.name} not ready, waiting...", true)
|
266
277
|
end
|
267
278
|
|
268
|
-
|
269
|
-
|
279
|
+
if volume_attach?(instance, volume)
|
280
|
+
vdev = @volume_service.volumes.find_by_id(volume.id).attachments.first['device']
|
281
|
+
mkfs_command = [
|
282
|
+
["sudo parted --script #{vdev} mklabel gpt mkpart primary 1 100%", ''],
|
283
|
+
["sudo mkfs.#{VOLUME_FILESYSTEM} -Fq #{vdev}1", '']
|
284
|
+
]
|
285
|
+
with_ssh(instance, username) do |ssh|
|
286
|
+
mkfs_command.each do |command, expected|
|
287
|
+
result = ssh.exec!(command)
|
288
|
+
next unless result != expected
|
289
|
+
error_log(
|
290
|
+
instance.logger,
|
291
|
+
'error',
|
292
|
+
"Failure while running '#{command}':\n\texpected '#{expected}'\n\tgot '#{result}'",
|
293
|
+
true
|
294
|
+
)
|
295
|
+
return false
|
296
|
+
end
|
297
|
+
end
|
298
|
+
mount = volume_mount_unmount?(instance, username, volume)
|
299
|
+
detach = volume_detach?(instance, volume)
|
300
|
+
else
|
301
|
+
error_log(instance.logger, 'error', "Volume '#{volume.id}' failed to attach.", true)
|
302
|
+
return false
|
270
303
|
end
|
271
304
|
|
272
|
-
if
|
273
|
-
error_log(instance.logger, 'info', "\
|
305
|
+
if mount && detach
|
306
|
+
error_log(instance.logger, 'info', "\nVolume testing passed!.", true)
|
274
307
|
true
|
275
308
|
else
|
276
309
|
error_log(
|
277
310
|
instance.logger,
|
278
311
|
'error',
|
279
|
-
"\
|
312
|
+
"\nVolume mounted: #{mount} detached: #{detach}.",
|
280
313
|
true
|
281
314
|
)
|
282
315
|
error_log(instance.logger, 'error', "\nEncountered failures.", true)
|
283
316
|
false
|
284
317
|
end
|
318
|
+
error_log(instance.logger, 'info', "Deleting volume #{volume.id}.", true)
|
319
|
+
volume.destroy if volume.ready?
|
285
320
|
end
|
286
321
|
|
287
322
|
# A helper method to execute a series of commands remotely on an instance. This helper
|
@@ -294,7 +329,7 @@ class OpenStackTaster
|
|
294
329
|
instance.logger.progname = 'SSH'
|
295
330
|
begin
|
296
331
|
Net::SSH.start(
|
297
|
-
instance.addresses[
|
332
|
+
instance.addresses[@network_name].first['addr'],
|
298
333
|
username,
|
299
334
|
verbose: :info,
|
300
335
|
paranoid: false,
|
@@ -372,6 +407,7 @@ class OpenStackTaster
|
|
372
407
|
['sudo partprobe -s', nil],
|
373
408
|
["[ -d '#{mount}' ] || sudo mkdir #{mount}", ''],
|
374
409
|
["sudo mount #{vdev} #{mount}", ''],
|
410
|
+
["sudo sh -c 'echo -n #{file_contents} > #{mount}/#{file_name}'", ''],
|
375
411
|
["sudo cat #{mount}/#{file_name}", file_contents],
|
376
412
|
["sudo umount #{mount}", '']
|
377
413
|
]
|
@@ -405,7 +441,8 @@ class OpenStackTaster
|
|
405
441
|
puts 'Logging partition list and dmesg...'
|
406
442
|
|
407
443
|
record_info_commands = [
|
408
|
-
'
|
444
|
+
'lsblk -l',
|
445
|
+
'lsblk -fl',
|
409
446
|
'dmesg | tail -n 20'
|
410
447
|
]
|
411
448
|
|
@@ -60,6 +60,15 @@ control 'security-1.0' do
|
|
60
60
|
its('stdout') { should cmp(/\(ALL\) ((NO)*PASSWD)*: ALL/) }
|
61
61
|
end
|
62
62
|
end
|
63
|
+
end
|
64
|
+
|
65
|
+
control 'ports-1.0' do
|
66
|
+
impact 1.0
|
67
|
+
title 'Openstack Image Ports Test'
|
68
|
+
desc 'Tests the open ports of images used for Openstack.'
|
69
|
+
|
70
|
+
# Skip these tests if we detect openstack is installed
|
71
|
+
only_if { !file('/etc/keystone').exist? }
|
63
72
|
|
64
73
|
# ssh should be the only thing listening
|
65
74
|
describe port.where { protocol =~ /tcp/ && port != 22 } do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openstack_taster
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OSU Open Source Lab
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: inspec
|