inspec 0.31.0 → 0.32.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.
@@ -27,6 +27,7 @@ module Inspec
27
27
  @__profile_id = profile_id
28
28
  @__checks = []
29
29
  @__skip_rule = nil
30
+ @__merge_count = 0
30
31
 
31
32
  # evaluate the given definition
32
33
  instance_eval(&block) if block_given?
@@ -135,6 +136,10 @@ module Inspec
135
136
  rule.instance_variable_set(:@__skip_rule, value)
136
137
  end
137
138
 
139
+ def self.merge_count(rule)
140
+ rule.instance_variable_get(:@__merge_count)
141
+ end
142
+
138
143
  def self.prepare_checks(rule)
139
144
  msg = skip_status(rule)
140
145
  return checks(rule) unless msg
@@ -169,6 +174,8 @@ module Inspec
169
174
  dst.instance_variable_set(:@__checks, sc) unless sc.empty?
170
175
  sr = skip_status(src)
171
176
  set_skip_rule(dst, sr) unless sr.nil?
177
+ # increment merge count
178
+ dst.instance_variable_set(:@__merge_count, merge_count(dst) + 1)
172
179
  end
173
180
 
174
181
  private
@@ -18,7 +18,7 @@ module Inspec
18
18
  extend Forwardable
19
19
  attr_reader :backend, :rules, :attributes
20
20
  def initialize(conf = {})
21
- @rules = {}
21
+ @rules = []
22
22
  @conf = conf.dup
23
23
  @conf[:logger] ||= Logger.new(nil)
24
24
 
@@ -131,26 +131,37 @@ module Inspec
131
131
  profile.runner_context = ctx
132
132
  end
133
133
 
134
- append_content(ctx, tests, libs, options)
135
- end
136
-
137
- # Returns the profile context used to evaluate the given content.
138
- def append_content(ctx, tests, _libs, options = {})
139
134
  # evaluate the test content
140
- tests = [tests] unless tests.is_a? Array
141
- tests.each { |t| add_test_to_context(t, ctx) }
135
+ Array(tests).each { |t| add_test_to_context(t, ctx) }
142
136
 
143
137
  # merge and collect all attributes
144
138
  @attributes |= ctx.attributes
145
139
 
146
140
  # process the resulting rules
147
- filter_controls(ctx.rules, options[:controls]).each do |rule_id, rule|
148
- register_rule(rule_id, rule)
141
+ filter_controls(ctx.all_rules, options[:controls]).each do |rule|
142
+ register_rule(rule)
149
143
  end
150
144
 
151
145
  ctx
152
146
  end
153
147
 
148
+ # In some places we read the rules off of the runner, in other
149
+ # places we read it off of the profile context. To keep the API's
150
+ # the same, we provide an #all_rules method here as well.
151
+ def all_rules
152
+ @rules
153
+ end
154
+
155
+ def register_rules(ctx)
156
+ new_tests = false
157
+ ctx.rules.each do |rule_id, rule|
158
+ next if block_given? && !(yield rule_id, rule)
159
+ new_tests = true
160
+ register_rule(rule)
161
+ end
162
+ new_tests
163
+ end
164
+
154
165
  def_delegator :@test_collector, :run
155
166
  def_delegator :@test_collector, :report
156
167
  def_delegator :@test_collector, :reset
@@ -163,9 +174,9 @@ module Inspec
163
174
  ctx.load(content, test[:ref], test[:line])
164
175
  end
165
176
 
166
- def filter_controls(controls_map, include_list)
167
- return controls_map if include_list.nil? || include_list.empty?
168
- controls_map.select do |_, c|
177
+ def filter_controls(controls_array, include_list)
178
+ return controls_array if include_list.nil? || include_list.empty?
179
+ controls_array.select do |c|
169
180
  id = ::Inspec::Rule.rule_id(c)
170
181
  include_list.include?(id)
171
182
  end
@@ -214,8 +225,8 @@ module Inspec
214
225
  nil
215
226
  end
216
227
 
217
- def register_rule(rule_id, rule)
218
- @rules[rule_id] = rule
228
+ def register_rule(rule)
229
+ @rules << rule
219
230
  checks = ::Inspec::Rule.prepare_checks(rule)
220
231
  examples = checks.map do |m, a, b|
221
232
  get_check_example(m, a, b)
@@ -18,11 +18,6 @@ module Inspec
18
18
  reset
19
19
  end
20
20
 
21
- def reset
22
- reset_tests
23
- configure_output
24
- end
25
-
26
21
  # Create a new RSpec example group from arguments and block.
27
22
  #
28
23
  # @param [Type] *args list of arguments for this example
@@ -94,10 +89,11 @@ module Inspec
94
89
  # Empty the list of registered tests.
95
90
  #
96
91
  # @return [nil]
97
- def reset_tests
92
+ def reset
98
93
  @tests = RSpec::Core::World.new
99
94
  # resets "pending examples" in reporter
100
95
  RSpec.configuration.reset
96
+ configure_output
101
97
  end
102
98
 
103
99
  private
@@ -3,6 +3,7 @@
3
3
  # author: Christoph Hartmann
4
4
 
5
5
  require 'rspec/core/formatters/base_text_formatter'
6
+ require 'pry'
6
7
 
7
8
  module Inspec
8
9
  # A pry based shell for inspec. Given a runner (with a configured backend and
@@ -11,18 +12,25 @@ module Inspec
11
12
  class Shell
12
13
  def initialize(runner)
13
14
  @runner = runner
14
- # load and configure pry
15
- require 'pry'
16
- configure_pry
17
15
  end
18
16
 
19
17
  def start
20
- # store context to run commands in this context
21
- c = { content: 'binding.pry', ref: nil, line: nil }
22
- @runner.add_content(c, [])
18
+ # Create an in-memory empty runner so that we can add tests to it later.
19
+ # This context lasts for the duration of this "start" method call/pry
20
+ # session.
21
+ @ctx = @runner.create_context
22
+ configure_pry
23
+
24
+ # This will hold a single evaluation binding context as opened within
25
+ # the instance_eval context of the anonymous class that the profile
26
+ # context creates to evaluate each individual test file. We want to
27
+ # pretend like we are constantly appending to the same file and want
28
+ # to capture the local variable context from inside said class.
29
+ @ctx_binding = @ctx.load('binding')
30
+ @ctx_binding.pry
23
31
  end
24
32
 
25
- def configure_pry
33
+ def configure_pry # rubocop:disable Metrics/AbcSize
26
34
  # Remove all hooks and checks
27
35
  Pry.hooks.clear_all
28
36
  that = self
@@ -37,25 +45,34 @@ module Inspec
37
45
  Pry.prompt = [proc { "#{readline_ignore("\e[0;32m")}#{Pry.config.prompt_name}> #{readline_ignore("\e[0m")}" }]
38
46
 
39
47
  # Add a help menu as the default intro
40
- Pry.hooks.add_hook(:before_session, :intro) do
48
+ Pry.hooks.add_hook(:before_session, 'inspec_intro') do
41
49
  intro
42
50
  end
43
51
 
44
- # execute describe blocks
45
- Pry.hooks.add_hook(:after_eval, 'run_controls') do |output, _binding, _pry_|
46
- next unless output.is_a?(Inspec::Rule)
47
- # reset tests, register the control and execute the runner
52
+ # Track the rules currently registered and what their merge count is.
53
+ Pry.hooks.add_hook(:before_eval, 'inspec_before_eval') do
54
+ @current_eval_rules = @ctx.rules.each_with_object({}) do |(rule_id, rule), h|
55
+ h[rule_id] = Inspec::Rule.merge_count(rule)
56
+ end
48
57
  @runner.reset
49
- @runner.method(:register_rule).call(output.id, output)
50
- @runner.run
58
+ end
59
+
60
+ # After pry has evaluated a commanding within the binding context of a
61
+ # test file, register all the rules it discovered.
62
+ Pry.hooks.add_hook(:after_eval, 'inspec_after_eval') do
63
+ @current_eval_new_tests =
64
+ @runner.register_rules(@ctx) do |rule_id, rule|
65
+ @current_eval_rules[rule_id] != Inspec::Rule.merge_count(rule)
66
+ end
67
+ @runner.run if @current_eval_new_tests
51
68
  end
52
69
 
53
70
  # Don't print out control class inspection when the user uses DSL methods.
54
71
  # Instead produce a result of evaluating their control.
55
- Pry.config.print = proc do |_output, value, pry_|
56
- next if value.is_a?(Inspec::Rule)
57
- pry_.pager.open do |pager|
58
- pager.print pry_.config.output_prefix
72
+ Pry.config.print = proc do |_output_, value, pry|
73
+ next if @current_eval_new_tests
74
+ pry.pager.open do |pager|
75
+ pager.print pry.config.output_prefix
59
76
  Pry::ColorPrinter.pp(value, pager, Pry::Terminal.width! - 1)
60
77
  end
61
78
  end
@@ -141,12 +158,4 @@ EOF
141
158
  puts Inspec::Resource.registry.keys.join(' ')
142
159
  end
143
160
  end
144
-
145
- class NoSummaryFormatter < RSpec::Core::Formatters::BaseTextFormatter
146
- RSpec::Core::Formatters.register self, :dump_summary
147
-
148
- def dump_summary(*_args)
149
- # output nothing
150
- end
151
- end
152
161
  end
@@ -1,7 +1,8 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  # author: Dominik Richter
3
4
  # author: Christoph Hartmann
4
5
 
5
6
  module Inspec
6
- VERSION = '0.31.0'.freeze
7
+ VERSION = '0.32.0'.freeze
7
8
  end
@@ -32,6 +32,10 @@ module Inspec::Resources
32
32
  describe host('example.com') do
33
33
  it { should be_reachable }
34
34
  end
35
+
36
+ describe host('example.com', port: '80') do
37
+ it { should be_reachable }
38
+ end
35
39
  "
36
40
 
37
41
  def initialize(hostname, params = {})
@@ -49,7 +53,7 @@ module Inspec::Resources
49
53
  end
50
54
  end
51
55
 
52
- # if we get the IP adress, the host is resolvable
56
+ # if we get the IP address, the host is resolvable
53
57
  def resolvable?(type = nil)
54
58
  warn "The `host` resource ignores #{type} parameters. Continue to resolve host." if !type.nil?
55
59
  resolve.nil? || resolve.empty? ? false : true
@@ -60,7 +64,7 @@ module Inspec::Resources
60
64
  ping.nil? ? false : ping
61
65
  end
62
66
 
63
- # returns all A records of the IP adress, will return an array
67
+ # returns all A records of the IP address, will return an array
64
68
  def ipaddress
65
69
  resolve.nil? || resolve.empty? ? nil : resolve
66
70
  end
@@ -122,9 +126,7 @@ module Inspec::Resources
122
126
  # TCP and port: Test-NetConnection -ComputerName www.microsoft.com -RemotePort 80
123
127
  request = "Test-NetConnection -ComputerName #{hostname}"
124
128
  request += " -RemotePort #{port}" unless port.nil?
125
- request += '| Select-Object -Property ComputerName, RemoteAddress, RemotePort, SourceAddress, PingSucceeded | ConvertTo-Json'
126
- p request
127
- request += '| Select-Object -Property ComputerName, PingSucceeded | ConvertTo-Json'
129
+ request += '| Select-Object -Property ComputerName, TcpTestSucceeded, PingSucceeded | ConvertTo-Json'
128
130
  cmd = inspec.command(request)
129
131
 
130
132
  begin
@@ -133,7 +135,12 @@ module Inspec::Resources
133
135
  return nil
134
136
  end
135
137
 
136
- ping['PingSucceeded']
138
+ # Logic being if you provided a port you wanted to check it was open
139
+ if port.nil?
140
+ ping['PingSucceeded']
141
+ else
142
+ ping['TcpTestSucceeded']
143
+ end
137
144
  end
138
145
 
139
146
  def resolve(hostname)
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
  # check for site in IIS
3
4
  # Usage:
4
5
  # describe iis_site('Default Web Site') do
@@ -21,7 +21,7 @@ module Inspec::Resources
21
21
  "
22
22
 
23
23
  # reuse helper methods from backend
24
- %w{aix? redhat? debian? suse? bsd? solaris? linux? unix? windows? hpux?}.each do |os_family|
24
+ %w{aix? redhat? debian? suse? bsd? solaris? linux? unix? windows? hpux? darwin?}.each do |os_family|
25
25
  define_method(os_family.to_sym) do
26
26
  inspec.backend.os.send(os_family)
27
27
  end
@@ -184,14 +184,27 @@ module Inspec::Resources
184
184
  end
185
185
  end
186
186
 
187
- # Determines the installed packages on Windows
188
- # Currently we use 'Get-WmiObject -Class Win32_Product' as a detection method
189
- # TODO: evaluate if alternative methods as proposed by Microsoft are still valid:
187
+ # Determines the installed packages on Windows using the Windows package registry entries.
190
188
  # @see: http://blogs.technet.com/b/heyscriptingguy/archive/2013/11/15/use-powershell-to-find-installed-software.aspx
191
189
  class WindowsPkg < PkgManagement
192
190
  def info(package_name)
191
+ search_paths = [
192
+ 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
193
+ 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
194
+ ]
195
+
196
+ # add 64 bit search paths
197
+ if inspec.os.arch == 'x86_64'
198
+ search_paths << 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
199
+ search_paths << 'HKCU:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
200
+ end
201
+
193
202
  # Find the package
194
- cmd = inspec.command("Get-WmiObject -Class Win32_Product | Where-Object {$_.Name -eq '#{package_name}'} | Select-Object -Property Name,Version,Vendor,PackageCode,Caption,Description | ConvertTo-Json")
203
+ cmd = inspec.command <<-EOF.gsub(/^\s*/, '')
204
+ Get-ItemProperty (@("#{search_paths.join('", "')}") | Where-Object { Test-Path $_ }) |
205
+ Where-Object { $_.DisplayName -like "#{package_name}*" -or $_.PSChildName -like "#{package_name}" } |
206
+ Select-Object -Property DisplayName,DisplayVersion | ConvertTo-Json
207
+ EOF
195
208
 
196
209
  begin
197
210
  package = JSON.parse(cmd.stdout)
@@ -199,10 +212,13 @@ module Inspec::Resources
199
212
  return nil
200
213
  end
201
214
 
215
+ # What if we match multiple packages? just pick the first one for now.
216
+ package = package[0] if package.is_a?(Array)
217
+
202
218
  {
203
- name: package['Name'],
219
+ name: package['DisplayName'],
204
220
  installed: true,
205
- version: package['Version'],
221
+ version: package['DisplayVersion'],
206
222
  type: 'windows',
207
223
  }
208
224
  end
@@ -5,17 +5,6 @@
5
5
  require 'utils/parser'
6
6
  require 'utils/filter'
7
7
 
8
- # Usage:
9
- # describe port(80) do
10
- # it { should be_listening }
11
- # its('protocol') {should eq 'tcp'}
12
- # end
13
- #
14
- # not supported serverspec syntax
15
- # describe port(80) do
16
- # it { should be_listening.with('tcp') }
17
- # end
18
- #
19
8
  # TODO: currently we return local ip only
20
9
  # TODO: improve handling of same port on multiple interfaces
21
10
  module Inspec::Resources
@@ -26,6 +15,7 @@ module Inspec::Resources
26
15
  describe port(80) do
27
16
  it { should be_listening }
28
17
  its('protocols') {should eq ['tcp']}
18
+ its('addresses') {should eq ['127.0.0.1']}
29
19
  end
30
20
 
31
21
  describe port.where { protocol =~ /tcp/ && port > 80 } do
@@ -77,6 +77,7 @@ module Inspec::Resources
77
77
  it { should be_enabled }
78
78
  it { should be_running }
79
79
  its('type') { should be 'systemd' }
80
+ its ('startmode') { should be 'Auto'}
80
81
  end
81
82
 
82
83
  describe service('service_name').runlevels(3, 5) do
@@ -210,6 +211,12 @@ module Inspec::Resources
210
211
  info[:description]
211
212
  end
212
213
 
214
+ # returns the service start up mode from info
215
+ def startmode
216
+ return nil if info.nil?
217
+ info[:startmode]
218
+ end
219
+
213
220
  def to_s
214
221
  "Service #{@service_name}"
215
222
  end
@@ -547,6 +554,7 @@ module Inspec::Resources
547
554
  #
548
555
  # Until StartMode is not added to Get-Service, we need to do a workaround
549
556
  # @see: https://connect.microsoft.com/PowerShell/feedback/details/424948/i-would-like-to-see-the-property-starttype-added-to-get-services
557
+ # Also see: https://msdn.microsoft.com/en-us/library/aa384896(v=vs.85).aspx
550
558
  # Use the following powershell to determine the start mode
551
559
  # PS: Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $name -or $_.DisplayName -eq $name} | Select-Object -Prop
552
560
  # erty Name, StartMode, State, Status | ConvertTo-Json
@@ -587,6 +595,7 @@ module Inspec::Resources
587
595
  installed: true,
588
596
  running: service_running?(service),
589
597
  enabled: service_enabled?(service),
598
+ startmode: service['WMI']['StartMode'],
590
599
  type: 'windows',
591
600
  }
592
601
  end
@@ -438,7 +438,7 @@ module Inspec::Resources
438
438
  ConvertTo-Json
439
439
  EOH
440
440
 
441
- cmd = inspec.script(script)
441
+ cmd = inspec.powershell(script)
442
442
 
443
443
  # cannot rely on exit code for now, successful command returns exit code 1
444
444
  # return nil if cmd.exit_status != 0, try to parse json
@@ -448,18 +448,18 @@ module Inspec::Resources
448
448
  return nil
449
449
  end
450
450
 
451
- user = params['User']['Caption'] unless params['User'].nil?
452
- groups = params['Groups']
451
+ user_hash = params['User'] || {}
452
+ group_hashes = params['Groups'] || []
453
453
  # if groups is no array, generate one
454
- groups = [groups] if !groups.is_a?(Array)
455
- groups = groups.map { |grp| grp['Caption'] } unless params['Groups'].nil?
454
+ group_hashes = [group_hashes] unless group_hashes.is_a?(Array)
455
+ group_names = group_hashes.map { |grp| grp['Caption'] }
456
456
 
457
457
  {
458
- uid: nil,
459
- user: user,
458
+ uid: user_hash['SID'],
459
+ user: user_hash['Caption'],
460
460
  gid: nil,
461
461
  group: nil,
462
- groups: groups,
462
+ groups: group_names,
463
463
  }
464
464
  end
465
465