inspec 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
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