inspec 0.9.2 → 0.9.3

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +272 -49
  3. data/Gemfile +1 -0
  4. data/README.md +9 -0
  5. data/Rakefile +12 -0
  6. data/docs/resources.rst +56 -56
  7. data/examples/test-kitchen/test/integration/default/web_spec.rb +1 -1
  8. data/inspec.gemspec +8 -3
  9. data/lib/inspec/profile_context.rb +2 -1
  10. data/lib/inspec/runner.rb +8 -0
  11. data/lib/inspec/shell.rb +2 -2
  12. data/lib/inspec/version.rb +1 -1
  13. data/lib/resources/command.rb +2 -0
  14. data/lib/resources/os_env.rb +38 -6
  15. data/lib/resources/port.rb +16 -4
  16. data/lib/resources/registry_key.rb +113 -17
  17. data/lib/resources/script.rb +1 -3
  18. data/lib/resources/security_policy.rb +1 -1
  19. data/lib/resources/service.rb +21 -15
  20. data/lib/utils/simpleconfig.rb +1 -1
  21. data/test/helper.rb +4 -2
  22. data/test/integration/cookbooks/os_prepare/recipes/default.rb +2 -0
  23. data/test/integration/cookbooks/os_prepare/recipes/file.rb +16 -12
  24. data/test/integration/cookbooks/os_prepare/recipes/registry_key.rb +69 -0
  25. data/test/integration/cookbooks/os_prepare/recipes/service.rb +12 -0
  26. data/test/integration/{default → test/integration/default}/_debug_spec.rb +0 -0
  27. data/test/integration/{default → test/integration/default}/apt_spec.rb +0 -0
  28. data/test/integration/{default → test/integration/default}/file_spec.rb +0 -0
  29. data/test/integration/{default → test/integration/default}/group_spec.rb +0 -0
  30. data/test/integration/{default → test/integration/default}/kernel_module_spec.rb +0 -0
  31. data/test/integration/{default → test/integration/default}/kernel_parameter_spec.rb +0 -0
  32. data/test/integration/{default → test/integration/default}/package_spec.rb +0 -0
  33. data/test/integration/test/integration/default/port_spec.rb +9 -0
  34. data/test/integration/test/integration/default/registry_key_spec.rb +53 -0
  35. data/test/integration/{default → test/integration/default}/service_spec.rb +14 -1
  36. data/test/integration/test/integration/default/user_spec.rb +62 -0
  37. data/test/unit/mock/cmd/$env-PATH +1 -0
  38. data/test/unit/mock/cmd/env +1 -0
  39. data/test/unit/mock/cmd/reg_schedule +6 -1
  40. data/test/unit/profile_context_test.rb +14 -3
  41. data/test/unit/resources/os_env_test.rb +6 -1
  42. data/test/unit/resources/registry_key_test.rb +2 -3
  43. metadata +32 -22
  44. data/test/integration/default/user_spec.rb +0 -44
  45. data/test/unit/mock/cmd/PATH +0 -1
@@ -8,7 +8,7 @@ describe package('nginx') do
8
8
  end
9
9
 
10
10
  # extend tests with metadata
11
- rule '01' do
11
+ control '01' do
12
12
  impact 0.7
13
13
  title 'Verify nginx service'
14
14
  desc 'Ensures nginx service is up and running'
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(README.md Rakefile MAINTAINERS.toml MAINTAINERS.md LICENSE inspec.gemspec Gemfile CHANGELOG.md .rubocop.yml) +
17
- Dir.glob("{bin,docs,examples,lib,tasks,test}/**/*", File::FNM_DOTMATCH).reject { |f| File.directory?(f) }
18
- spec.executables = %w( inspec )
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
- @runner.add_content({
17
- content: 'binding.pry', ref: __FILE__, line: __LINE__}, [])
16
+ c = { content: 'binding.pry', ref: __FILE__, line: __LINE__ }
17
+ @runner.add_content(c, [])
18
18
  @runner.run
19
19
  end
20
20
 
@@ -3,5 +3,5 @@
3
3
  # author: Christoph Hartmann
4
4
 
5
5
  module Inspec
6
- VERSION = '0.9.2'
6
+ VERSION = '0.9.3'
7
7
  end
@@ -14,6 +14,8 @@
14
14
 
15
15
  class Cmd < Inspec.resource(1)
16
16
  name 'command'
17
+ attr_reader :command
18
+
17
19
  def initialize(cmd)
18
20
  @command = cmd
19
21
  end
@@ -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
- cmd = inspec.command("su - root -c 'echo $#{env}'")
21
- @content = cmd.stdout.chomp
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(':', -1)
32
+ @content.nil? ? [] : @content.split(path_separator, -1)
29
33
  end
30
34
 
31
35
  def to_s
32
- "Environment Variable #{@osenv}"
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
@@ -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+:)(\d+)$/.match(net_addr)
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+)$/.match(line)
202
- return {} if parsed.nil?
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 registry_value(path, key)
26
- cmd = "(Get-Item 'Registry::#{path}').GetValue('#{key}')"
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 convert_value(value)
33
- val = value.strip
34
- val = val.to_i if val.match(/^\d+$/)
35
- val
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, meth)
42
-
43
- # verify data
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
@@ -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
@@ -41,7 +41,7 @@ class SecurityPolicy < Inspec.resource(1)
41
41
 
42
42
  def method_missing(method)
43
43
  # load data if needed
44
- if (@loaded == false)
44
+ if @loaded == false
45
45
  load
46
46
  end
47
47
 
@@ -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
- cmd = inspec.command("initctl status #{service_name}")
153
- return nil if cmd.exit_status != 0
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
- match_running = /running/.match(cmd.stdout)
158
- !match_running.nil? ? (running = true) : (running = false)
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
- match_enabled = /^\s*start on/.match(config.stdout)
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
- cmd.exit_status == 0
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