inspec 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +272 -49
- data/Gemfile +1 -0
- data/README.md +9 -0
- data/Rakefile +12 -0
- data/docs/resources.rst +56 -56
- data/examples/test-kitchen/test/integration/default/web_spec.rb +1 -1
- data/inspec.gemspec +8 -3
- data/lib/inspec/profile_context.rb +2 -1
- data/lib/inspec/runner.rb +8 -0
- data/lib/inspec/shell.rb +2 -2
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/command.rb +2 -0
- data/lib/resources/os_env.rb +38 -6
- data/lib/resources/port.rb +16 -4
- data/lib/resources/registry_key.rb +113 -17
- data/lib/resources/script.rb +1 -3
- data/lib/resources/security_policy.rb +1 -1
- data/lib/resources/service.rb +21 -15
- data/lib/utils/simpleconfig.rb +1 -1
- data/test/helper.rb +4 -2
- data/test/integration/cookbooks/os_prepare/recipes/default.rb +2 -0
- data/test/integration/cookbooks/os_prepare/recipes/file.rb +16 -12
- data/test/integration/cookbooks/os_prepare/recipes/registry_key.rb +69 -0
- data/test/integration/cookbooks/os_prepare/recipes/service.rb +12 -0
- data/test/integration/{default → test/integration/default}/_debug_spec.rb +0 -0
- data/test/integration/{default → test/integration/default}/apt_spec.rb +0 -0
- data/test/integration/{default → test/integration/default}/file_spec.rb +0 -0
- data/test/integration/{default → test/integration/default}/group_spec.rb +0 -0
- data/test/integration/{default → test/integration/default}/kernel_module_spec.rb +0 -0
- data/test/integration/{default → test/integration/default}/kernel_parameter_spec.rb +0 -0
- data/test/integration/{default → test/integration/default}/package_spec.rb +0 -0
- data/test/integration/test/integration/default/port_spec.rb +9 -0
- data/test/integration/test/integration/default/registry_key_spec.rb +53 -0
- data/test/integration/{default → test/integration/default}/service_spec.rb +14 -1
- data/test/integration/test/integration/default/user_spec.rb +62 -0
- data/test/unit/mock/cmd/$env-PATH +1 -0
- data/test/unit/mock/cmd/env +1 -0
- data/test/unit/mock/cmd/reg_schedule +6 -1
- data/test/unit/profile_context_test.rb +14 -3
- data/test/unit/resources/os_env_test.rb +6 -1
- data/test/unit/resources/registry_key_test.rb +2 -3
- metadata +32 -22
- data/test/integration/default/user_spec.rb +0 -44
- data/test/unit/mock/cmd/PATH +0 -1
data/inspec.gemspec
CHANGED
@@ -13,9 +13,14 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.homepage = 'https://github.com/chef/inspec'
|
14
14
|
spec.license = 'Apache 2.0'
|
15
15
|
|
16
|
-
spec.files = %w
|
17
|
-
|
18
|
-
|
16
|
+
spec.files = %w{
|
17
|
+
README.md Rakefile MAINTAINERS.toml MAINTAINERS.md LICENSE inspec.gemspec
|
18
|
+
Gemfile CHANGELOG.md .rubocop.yml
|
19
|
+
} + Dir.glob(
|
20
|
+
'{bin,docs,examples,lib,tasks,test}/**/*', File::FNM_DOTMATCH
|
21
|
+
).reject { |f| File.directory?(f) }
|
22
|
+
|
23
|
+
spec.executables = %w{ inspec }
|
19
24
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
25
|
spec.require_paths = ['lib']
|
21
26
|
|
@@ -5,6 +5,7 @@
|
|
5
5
|
require 'inspec/rule'
|
6
6
|
require 'inspec/dsl'
|
7
7
|
require 'rspec/core/dsl'
|
8
|
+
require 'securerandom'
|
8
9
|
|
9
10
|
module Inspec
|
10
11
|
class ProfileContext # rubocop:disable Metrics/ClassLength
|
@@ -110,7 +111,7 @@ module Inspec
|
|
110
111
|
define_method :describe do |*args, &block|
|
111
112
|
path = block.source_location[0]
|
112
113
|
line = block.source_location[1]
|
113
|
-
id = "#{File.basename(path)}:#{line}"
|
114
|
+
id = "#{File.basename(path)}:#{line} #{SecureRandom.hex}"
|
114
115
|
rule = rule_class.new(id, {}) do
|
115
116
|
describe(*args, &block)
|
116
117
|
end
|
data/lib/inspec/runner.rb
CHANGED
@@ -52,6 +52,14 @@ module Inspec
|
|
52
52
|
tests = items.find_all { |i| i[:type] == :test }
|
53
53
|
libs = items.find_all { |i| i[:type] == :library }
|
54
54
|
|
55
|
+
# Ensure each test directory exists on the $LOAD_PATH. This
|
56
|
+
# will ensure traditional RSpec-isms like `require 'spec_helper'`
|
57
|
+
# continue to work.
|
58
|
+
tests.flatten.each do |test|
|
59
|
+
test_directory = File.dirname(test[:ref])
|
60
|
+
$LOAD_PATH.unshift test_directory unless $LOAD_PATH.include?(test_directory)
|
61
|
+
end
|
62
|
+
|
55
63
|
# add all tests (raw) to the runtime
|
56
64
|
tests.flatten.each do |test|
|
57
65
|
add_content(test, libs)
|
data/lib/inspec/shell.rb
CHANGED
@@ -13,8 +13,8 @@ module Inspec
|
|
13
13
|
|
14
14
|
def start
|
15
15
|
# store context to run commands in this context
|
16
|
-
|
17
|
-
|
16
|
+
c = { content: 'binding.pry', ref: __FILE__, line: __LINE__ }
|
17
|
+
@runner.add_content(c, [])
|
18
18
|
@runner.run
|
19
19
|
end
|
20
20
|
|
data/lib/inspec/version.rb
CHANGED
data/lib/resources/command.rb
CHANGED
data/lib/resources/os_env.rb
CHANGED
@@ -11,24 +11,56 @@
|
|
11
11
|
# its(:split) { should_not include('.') }
|
12
12
|
# end
|
13
13
|
|
14
|
+
require 'utils/simpleconfig'
|
15
|
+
|
14
16
|
class OsEnv < Inspec.resource(1)
|
15
17
|
name 'os_env'
|
16
18
|
|
17
19
|
attr_reader :content
|
18
|
-
def initialize(env)
|
20
|
+
def initialize(env = nil)
|
19
21
|
@osenv = env
|
20
|
-
|
21
|
-
@content =
|
22
|
-
@content = nil if cmd.exit_status != 0
|
22
|
+
@content = nil
|
23
|
+
@content = value_for(env) unless env.nil?
|
23
24
|
end
|
24
25
|
|
25
26
|
def split
|
27
|
+
# we can't take advantage of `File::PATH_SEPARATOR` as code is
|
28
|
+
# evaluated on the host machine
|
29
|
+
path_separator = inspec.os.windows? ? ';' : ':'
|
26
30
|
# -1 is required to catch cases like dir1::dir2:
|
27
31
|
# where we have a trailing :
|
28
|
-
@content.nil? ? [] : @content.split(
|
32
|
+
@content.nil? ? [] : @content.split(path_separator, -1)
|
29
33
|
end
|
30
34
|
|
31
35
|
def to_s
|
32
|
-
|
36
|
+
if @osenv.nil?
|
37
|
+
'Environment variables'
|
38
|
+
else
|
39
|
+
"Environment variable #{@osenv}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def value_for(env)
|
46
|
+
command = if inspec.os.windows?
|
47
|
+
"$Env:#{env}"
|
48
|
+
else
|
49
|
+
'env'
|
50
|
+
end
|
51
|
+
|
52
|
+
out = inspec.command(command)
|
53
|
+
|
54
|
+
unless out.exit_status == 0
|
55
|
+
skip_resource "Can't read environment variables on #{os[:family]}. "\
|
56
|
+
"Tried `#{command}` which returned #{out.exit_status}"
|
57
|
+
end
|
58
|
+
|
59
|
+
if inspec.os.windows?
|
60
|
+
out.stdout.strip
|
61
|
+
else
|
62
|
+
params = SimpleConfig.new(out.stdout).params
|
63
|
+
params[env]
|
64
|
+
end
|
33
65
|
end
|
34
66
|
end
|
data/lib/resources/port.rb
CHANGED
@@ -24,7 +24,7 @@ class Port < Inspec.resource(1)
|
|
24
24
|
@cache = nil
|
25
25
|
|
26
26
|
case inspec.os[:family]
|
27
|
-
when 'ubuntu', 'debian', 'redhat', 'fedora', 'arch'
|
27
|
+
when 'ubuntu', 'debian', 'redhat', 'fedora', 'centos', 'arch'
|
28
28
|
@port_manager = LinuxPorts.new(inspec)
|
29
29
|
when 'darwin'
|
30
30
|
@port_manager = DarwinPorts.new(inspec)
|
@@ -179,7 +179,7 @@ class LinuxPorts < PortsInfo
|
|
179
179
|
def parse_net_address(net_addr, protocol)
|
180
180
|
if protocol.eql?('tcp6') || protocol.eql?('udp6')
|
181
181
|
# prep for URI parsing, parse ip6 port
|
182
|
-
ip6 = /^(\S
|
182
|
+
ip6 = /^(\S+):(\d+)$/.match(net_addr)
|
183
183
|
ip6addr = ip6[1]
|
184
184
|
ip6addr = '::' if /^:::$/.match(ip6addr)
|
185
185
|
# build uri
|
@@ -193,16 +193,25 @@ class LinuxPorts < PortsInfo
|
|
193
193
|
port = ip_addr.port
|
194
194
|
end
|
195
195
|
[host, port]
|
196
|
+
rescue URI::InvalidURIError => e
|
197
|
+
warn "Could not parse #{net_addr}, #{e}"
|
198
|
+
nil
|
196
199
|
end
|
197
200
|
|
198
201
|
def parse_netstat_line(line)
|
199
202
|
# parse each line
|
200
203
|
# 1 - Proto, 2 - Recv-Q, 3 - Send-Q, 4 - Local Address, 5 - Foreign Address, 6 - State, 7 - Inode, 8 - PID/Program name
|
201
|
-
parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)
|
202
|
-
|
204
|
+
parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/.match(line)
|
205
|
+
|
206
|
+
return {} if parsed.nil? || line.match(/^proto/i)
|
203
207
|
|
204
208
|
# parse ip4 and ip6 addresses
|
205
209
|
protocol = parsed[1].downcase
|
210
|
+
|
211
|
+
# detect protocol if not provided
|
212
|
+
protocol += '6' if parsed[4].count(':') > 1 && %w{tcp udp}.include?(protocol)
|
213
|
+
|
214
|
+
# extract host and port information
|
206
215
|
host, port = parse_net_address(parsed[4], protocol)
|
207
216
|
|
208
217
|
# extract PID
|
@@ -261,6 +270,9 @@ class FreeBsdPorts < PortsInfo
|
|
261
270
|
port = ip_addr.port
|
262
271
|
end
|
263
272
|
[host, port]
|
273
|
+
rescue URI::InvalidURIError => e
|
274
|
+
warn "Could not parse #{net_addr}, #{e}"
|
275
|
+
nil
|
264
276
|
end
|
265
277
|
|
266
278
|
def parse_sockstat_line(line)
|
@@ -22,33 +22,129 @@ class RegistryKey < Inspec.resource(1)
|
|
22
22
|
@reg_key = reg_key
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
26
|
-
|
27
|
-
command_result ||= inspec.command(cmd)
|
28
|
-
val = { exit_code: command_result.exit_status.to_i, data: command_result.stdout }
|
29
|
-
val
|
25
|
+
def exists?
|
26
|
+
!registry_value(@reg_key).nil?
|
30
27
|
end
|
31
28
|
|
32
|
-
def
|
33
|
-
val =
|
34
|
-
val
|
35
|
-
|
29
|
+
def has_value?(value)
|
30
|
+
val = registry_value(@reg_key)
|
31
|
+
!val.nil? && val['(default)'.to_s]['value'] == value ? true : false
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_property?(property_name, property_type = nil)
|
35
|
+
val = registry_value(@reg_key)
|
36
|
+
!val.nil? && !val[property_name.to_s].nil? && (property_type.nil? || val[property_name.to_s]['type'] == map2type(property_type)) ? true : false
|
37
|
+
end
|
38
|
+
|
39
|
+
# deactivate rubocop, because we need to stay compatible with Serverspe
|
40
|
+
# rubocop:disable Style/OptionalArguments
|
41
|
+
def has_property_value?(property_name, property_type = nil, value)
|
42
|
+
# rubocop:enable Style/OptionalArguments
|
43
|
+
val = registry_value(@reg_key)
|
44
|
+
|
45
|
+
# convert value to binary if required
|
46
|
+
value = value.bytes if !property_type.nil? && map2type(property_type) == 3 && !value.is_a?(Array)
|
47
|
+
|
48
|
+
!val.nil? && val[property_name.to_s]['value'] == value && (property_type.nil? || val[property_name.to_s]['type'] == map2type(property_type)) ? true : false
|
36
49
|
end
|
37
50
|
|
38
51
|
# returns nil, if not existant or value
|
39
52
|
def method_missing(meth)
|
40
53
|
# get data
|
41
|
-
val = registry_value(@reg_key
|
42
|
-
|
43
|
-
|
44
|
-
if (val[:exit_code] == 0)
|
45
|
-
return convert_value(val[:data])
|
46
|
-
else
|
47
|
-
return nil
|
48
|
-
end
|
54
|
+
val = registry_value(@reg_key)
|
55
|
+
return nil if val.nil?
|
56
|
+
val[meth.to_s]['value']
|
49
57
|
end
|
50
58
|
|
51
59
|
def to_s
|
52
60
|
"Registry Key #{@name}"
|
53
61
|
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def registry_value(path)
|
66
|
+
return @registy_cache if defined?(@registy_cache)
|
67
|
+
|
68
|
+
# load registry key and all properties
|
69
|
+
script = <<-EOH
|
70
|
+
$reg = Get-Item 'Registry::#{path}'
|
71
|
+
$object = New-Object -Type PSObject
|
72
|
+
$reg.Property | ForEach-Object {
|
73
|
+
$key = $_
|
74
|
+
if ("(default)".Equals($key)) { $key = '' }
|
75
|
+
$value = New-Object psobject -Property @{
|
76
|
+
"value" = $reg.GetValue($key);
|
77
|
+
"type" = $reg.GetValueKind($key);
|
78
|
+
}
|
79
|
+
$object | Add-Member –MemberType NoteProperty –Name $_ –Value $value
|
80
|
+
}
|
81
|
+
$object | ConvertTo-Json
|
82
|
+
EOH
|
83
|
+
|
84
|
+
cmd = inspec.script(script)
|
85
|
+
|
86
|
+
# cannot rely on exit code for now, successful command returns exit code 1
|
87
|
+
# return nil if cmd.exit_status != 0, try to parse json
|
88
|
+
begin
|
89
|
+
@registy_cache = JSON.parse(cmd.stdout)
|
90
|
+
rescue JSON::ParserError => _e
|
91
|
+
@registy_cache = nil
|
92
|
+
end
|
93
|
+
|
94
|
+
@registy_cache
|
95
|
+
end
|
96
|
+
|
97
|
+
# Registry key value types
|
98
|
+
# @see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724884(v=vs.85).aspx
|
99
|
+
# REG_NONE 0
|
100
|
+
# REG_SZ 1
|
101
|
+
# REG_EXPAND_SZ 2
|
102
|
+
# REG_BINARY 3
|
103
|
+
# REG_DWORD 4
|
104
|
+
# REG_DWORD_LITTLE_ENDIAN 4
|
105
|
+
# REG_DWORD_BIG_ENDIAN 5
|
106
|
+
# REG_LINK 6
|
107
|
+
# REG_MULTI_SZ 7
|
108
|
+
# REG_RESOURCE_LIST 8
|
109
|
+
# REG_FULL_RESOURCE_DESCRIPTOR 9
|
110
|
+
# REG_RESOURCE_REQUIREMENTS_LIST 10
|
111
|
+
# REG_QWORD 11
|
112
|
+
# REG_QWORD_LITTLE_ENDIAN 11
|
113
|
+
def map2type(symbol)
|
114
|
+
options = {}
|
115
|
+
|
116
|
+
# chef symbols, we prefer those
|
117
|
+
options[:binary] = 3
|
118
|
+
options[:string] = 1
|
119
|
+
options[:multi_string] = 7
|
120
|
+
options[:expand_string] = 2
|
121
|
+
options[:dword] = 4
|
122
|
+
options[:dword_big_endian] = 5
|
123
|
+
options[:qword] = 11
|
124
|
+
|
125
|
+
# serverspec symbols
|
126
|
+
options[:type_string] = 1
|
127
|
+
options[:type_binary] = 3
|
128
|
+
options[:type_dword] = 4
|
129
|
+
options[:type_qword] = 11
|
130
|
+
options[:type_multistring] = 7
|
131
|
+
options[:type_expandstring] = 2
|
132
|
+
|
133
|
+
options[symbol]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# for compatability with serverspec
|
138
|
+
# this is deprecated syntax and will be removed in future versions
|
139
|
+
class WindowsRegistryKey < RegistryKey
|
140
|
+
name 'windows_registry_key'
|
141
|
+
|
142
|
+
def initialize(name)
|
143
|
+
deprecated
|
144
|
+
super(name)
|
145
|
+
end
|
146
|
+
|
147
|
+
def deprecated
|
148
|
+
warn '[DEPRECATION] `yumrepo(reponame)` is deprecated. Please use `yum.repo(reponame)` instead.'
|
149
|
+
end
|
54
150
|
end
|
data/lib/resources/script.rb
CHANGED
@@ -6,7 +6,6 @@
|
|
6
6
|
|
7
7
|
class Script < Cmd
|
8
8
|
name 'script'
|
9
|
-
attr_accessor :command
|
10
9
|
|
11
10
|
def initialize(script)
|
12
11
|
case inspec.os[:family]
|
@@ -19,8 +18,7 @@ class Script < Cmd
|
|
19
18
|
else
|
20
19
|
return skip_resource 'The `script` resource is not supported on your OS yet.'
|
21
20
|
end
|
22
|
-
|
23
|
-
@command = cmd
|
21
|
+
super(cmd)
|
24
22
|
end
|
25
23
|
|
26
24
|
# we cannot determine if a command exists, because that does not work for scripts
|
data/lib/resources/service.rb
CHANGED
@@ -149,39 +149,45 @@ end
|
|
149
149
|
class Upstart < ServiceManager
|
150
150
|
def info(service_name)
|
151
151
|
# get the status of upstart service
|
152
|
-
|
153
|
-
|
152
|
+
status = inspec.command("initctl status #{service_name}")
|
153
|
+
|
154
|
+
# fallback for systemv services, those are not handled via `initctl`
|
155
|
+
return SysV.new(inspec).info(service_name) if status.exit_status.to_i != 0
|
154
156
|
|
155
157
|
# @see: http://upstart.ubuntu.com/cookbook/#job-states
|
156
158
|
# grep for running to indicate the service is there
|
157
|
-
|
158
|
-
|
159
|
+
running = !status.stdout[/running/].nil?
|
160
|
+
|
161
|
+
{
|
162
|
+
name: service_name,
|
163
|
+
description: nil,
|
164
|
+
installed: true,
|
165
|
+
running: running,
|
166
|
+
enabled: info_enabled(status, service_name),
|
167
|
+
type: 'upstart',
|
168
|
+
}
|
169
|
+
end
|
159
170
|
|
171
|
+
private
|
172
|
+
|
173
|
+
def info_enabled(status, service_name)
|
160
174
|
# check if a service is enabled
|
161
175
|
# http://upstart.ubuntu.com/cookbook/#determine-if-a-job-is-disabled
|
162
176
|
# $ initctl show-config $job | grep -q "^ start on" && echo enabled || echo disabled
|
163
177
|
# Ubuntu 10.04 show-config is not supported
|
164
178
|
# @see http://manpages.ubuntu.com/manpages/maverick/man8/initctl.8.html
|
165
179
|
config = inspec.command("initctl show-config #{service_name}")
|
166
|
-
|
167
|
-
!match_enabled.nil? ? (enabled = true) : (enabled = false)
|
180
|
+
enabled = !config.stdout[/^\s*start on/].nil?
|
168
181
|
|
169
182
|
# implement fallback for Ubuntu 10.04
|
170
183
|
if inspec.os[:family] == 'ubuntu' &&
|
171
184
|
inspec.os[:release].to_f >= 10.04 &&
|
172
185
|
inspec.os[:release].to_f < 12.04 &&
|
173
|
-
|
186
|
+
status.exit_status == 0
|
174
187
|
enabled = true
|
175
188
|
end
|
176
189
|
|
177
|
-
|
178
|
-
name: service_name,
|
179
|
-
description: nil,
|
180
|
-
installed: true,
|
181
|
-
running: running,
|
182
|
-
enabled: enabled,
|
183
|
-
type: 'upstart',
|
184
|
-
}
|
190
|
+
enabled
|
185
191
|
end
|
186
192
|
end
|
187
193
|
|