inspec-core 4.16.0 → 4.17.7

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/lib/inspec.rb +0 -1
  3. data/lib/inspec/backend.rb +7 -0
  4. data/lib/inspec/base_cli.rb +2 -0
  5. data/lib/inspec/cli.rb +3 -10
  6. data/lib/inspec/config.rb +3 -4
  7. data/lib/inspec/control_eval_context.rb +5 -3
  8. data/lib/inspec/dsl.rb +24 -1
  9. data/lib/inspec/errors.rb +0 -26
  10. data/lib/inspec/file_provider.rb +33 -43
  11. data/lib/inspec/formatters/base.rb +1 -0
  12. data/lib/inspec/impact.rb +2 -0
  13. data/lib/inspec/input.rb +410 -0
  14. data/lib/inspec/input_registry.rb +10 -1
  15. data/lib/inspec/objects.rb +3 -1
  16. data/lib/inspec/objects/input.rb +5 -387
  17. data/lib/inspec/objects/tag.rb +1 -1
  18. data/lib/inspec/plugin/v1/plugin_types/resource.rb +16 -5
  19. data/lib/inspec/plugin/v2/activator.rb +4 -8
  20. data/lib/inspec/plugin/v2/loader.rb +19 -3
  21. data/lib/inspec/profile.rb +1 -1
  22. data/lib/inspec/profile_context.rb +1 -1
  23. data/lib/inspec/reporters/json.rb +70 -88
  24. data/lib/inspec/resource.rb +1 -0
  25. data/lib/inspec/resources.rb +9 -2
  26. data/lib/inspec/resources/aide_conf.rb +4 -0
  27. data/lib/inspec/resources/apt.rb +19 -19
  28. data/lib/inspec/resources/etc_fstab.rb +4 -0
  29. data/lib/inspec/resources/etc_hosts.rb +4 -0
  30. data/lib/inspec/resources/firewalld.rb +4 -0
  31. data/lib/inspec/resources/json.rb +10 -3
  32. data/lib/inspec/resources/mssql_session.rb +1 -1
  33. data/lib/inspec/resources/platform.rb +18 -13
  34. data/lib/inspec/resources/postfix_conf.rb +6 -2
  35. data/lib/inspec/resources/security_identifier.rb +4 -0
  36. data/lib/inspec/resources/sys_info.rb +65 -4
  37. data/lib/inspec/resources/user.rb +1 -0
  38. data/lib/inspec/rule.rb +68 -6
  39. data/lib/inspec/runner.rb +6 -1
  40. data/lib/inspec/runner_rspec.rb +1 -0
  41. data/lib/inspec/shell.rb +8 -1
  42. data/lib/inspec/utils/pkey_reader.rb +1 -1
  43. data/lib/inspec/version.rb +1 -1
  44. data/lib/matchers/matchers.rb +2 -0
  45. metadata +4 -2
@@ -57,6 +57,10 @@ module Inspec::Resources
57
57
  where { mount_point == "/home" }.entries[0].mount_options
58
58
  end
59
59
 
60
+ def to_s
61
+ "File System Table File (fstab)"
62
+ end
63
+
60
64
  private
61
65
 
62
66
  def read_content
@@ -37,6 +37,10 @@ module Inspec::Resources
37
37
  .register_column(:all_host_names, field: "all_host_names")
38
38
  .install_filter_methods_on_resource(self, :params)
39
39
 
40
+ def to_s
41
+ "Hosts File"
42
+ end
43
+
40
44
  private
41
45
 
42
46
  def default_hosts_file_path
@@ -88,6 +88,10 @@ module Inspec::Resources
88
88
  firewalld_command("--zone=#{query_zone} --query-rich-rule='#{rule}'") == "yes"
89
89
  end
90
90
 
91
+ def to_s
92
+ "Firewall Rules"
93
+ end
94
+
91
95
  private
92
96
 
93
97
  def active_zones
@@ -93,10 +93,17 @@ module Inspec::Resources
93
93
  end
94
94
 
95
95
  def load_raw_from_command(command)
96
- command_output = inspec.command(command).stdout
97
- raise Inspec::Exceptions::ResourceSkipped, "No output from command: #{command}" if command_output.nil? || command_output.empty?
96
+ result = inspec.command(command)
98
97
 
99
- command_output
98
+ return result.stdout unless result.stdout.empty?
99
+
100
+ msg = if result.stderr.empty?
101
+ "No JSON output, STDERR was empty"
102
+ else
103
+ "No JSON output, STDERR:\n #{result.stderr}"
104
+ end
105
+
106
+ raise Inspec::Exceptions::ResourceFailed, msg
100
107
  end
101
108
 
102
109
  # for resources the subclass JsonConfig, this allows specification of the resource
@@ -53,7 +53,7 @@ module Inspec::Resources
53
53
  end
54
54
 
55
55
  def query(q) # rubocop:disable Metrics/PerceivedComplexity
56
- escaped_query = q.gsub(/\\/, '\\\\').gsub(/"/, '\\"').gsub(/\$/, '\\$')
56
+ escaped_query = q.gsub(/\\/, '\\\\').gsub(/"/, '""').gsub(/\$/, '\\$')
57
57
  # surpress 'x rows affected' in SQLCMD with 'set nocount on;'
58
58
  cmd_string = "sqlcmd -Q \"set nocount on; #{escaped_query}\" -W -w 1024 -s ','"
59
59
  cmd_string += " -U '#{@user}' -P '#{@password}'" unless @user.nil? || @password.nil?
@@ -1,3 +1,5 @@
1
+ require "inspec/resource"
2
+
1
3
  module Inspec::Resources
2
4
  class PlatformResource < Inspec.resource(1)
3
5
  name "platform"
@@ -67,19 +69,22 @@ module Inspec::Resources
67
69
  return true if supports.nil? || supports.empty?
68
70
 
69
71
  status = true
70
- supports.each do |s|
71
- s.each do |k, v|
72
- if %i{os_family os-family platform_family platform-family}.include?(k)
73
- status = in_family?(v)
74
- elsif %i{os platform}.include?(k)
75
- status = platform?(v)
76
- elsif %i{os_name os-name platform_name platform-name}.include?(k)
77
- status = name == v
78
- elsif k == :release
79
- status = check_release(v)
80
- else
81
- status = false
82
- end
72
+ supports.each do |support|
73
+ support.each do |k, v|
74
+ status =
75
+ case k
76
+ when :os_family, :"os-family", :platform_family, :"platform-family" then
77
+ in_family?(v)
78
+ when :os, :platform then
79
+ platform?(v)
80
+ when :os_name, :"os-name", :platform_name, :"platform-name" then
81
+ name == v
82
+ when :release then
83
+ check_release(v)
84
+ else
85
+ false
86
+ end
87
+
83
88
  break if status == false
84
89
  end
85
90
  return true if status == true
@@ -11,7 +11,7 @@ module Inspec::Resources
11
11
  def initialize(*opts)
12
12
  @params = {}
13
13
  if opts.length == 1
14
- @raw_content = load_raw_content(opts)
14
+ @raw_content = load_raw_content(opts[0])
15
15
  else
16
16
  @raw_content = load_raw_content("/etc/postfix/main.cf")
17
17
  end
@@ -22,10 +22,14 @@ module Inspec::Resources
22
22
  SimpleConfig.new(content).params
23
23
  end
24
24
 
25
+ def to_s
26
+ "Postfix Mail Transfer Agent"
27
+ end
28
+
25
29
  private
26
30
 
27
31
  def resource_base_name
28
- "POSTFIX_CONF"
32
+ "Postfix Config"
29
33
  end
30
34
  end
31
35
  end
@@ -47,6 +47,10 @@ module Inspec::Resources
47
47
  @sids.key?(@name)
48
48
  end
49
49
 
50
+ def to_s
51
+ "Security Identifier"
52
+ end
53
+
50
54
  private
51
55
 
52
56
  def fetch_sids
@@ -13,20 +13,77 @@ module Inspec::Resources
13
13
  describe sys_info do
14
14
  its('hostname') { should eq 'example.com' }
15
15
  end
16
+
17
+ describe sys_info do
18
+ its('fqdn') { should eq 'user.example.com' }
19
+ end
20
+
16
21
  EXAMPLE
17
22
 
23
+ %w{ domain fqdn ip_address short }.each do |opt|
24
+ define_method(opt.to_sym) do
25
+ hostname(opt)
26
+ end
27
+ end
28
+
18
29
  # returns the hostname of the local system
19
- def hostname
30
+ def hostname(opt = nil)
20
31
  os = inspec.os
21
- if os.linux? || os.darwin?
22
- inspec.command("hostname").stdout.chomp
32
+ if os.linux?
33
+ linux_hostname(opt)
34
+ elsif os.darwin?
35
+ mac_hostname(opt)
23
36
  elsif os.windows?
24
- inspec.powershell("$env:computername").stdout.chomp
37
+ if !opt.nil?
38
+ skip_resource "The `sys_info.hostname` resource is not supported with that option on your OS."
39
+ else
40
+ inspec.powershell("$env:computername").stdout.chomp
41
+ end
25
42
  else
26
43
  skip_resource "The `sys_info.hostname` resource is not supported on your OS yet."
27
44
  end
28
45
  end
29
46
 
47
+ def linux_hostname(opt = nil)
48
+ if !opt.nil?
49
+ opt = case opt
50
+ when "f", "long", "fqdn", "full"
51
+ " -f"
52
+ when "d", "domain"
53
+ " -d"
54
+ when "i", "ip_address"
55
+ " -I"
56
+ when "s", "short"
57
+ " -s"
58
+ else
59
+ "ERROR"
60
+ end
61
+ end
62
+ if opt == "ERROR"
63
+ skip_resource "The `sys_info.hostname` resource is not supported with that option on your OS."
64
+ else
65
+ inspec.command("hostname#{opt}").stdout.chomp
66
+ end
67
+ end
68
+
69
+ def mac_hostname(opt = nil)
70
+ if !opt.nil?
71
+ opt = case opt
72
+ when "f", "long", "fqdn", "full"
73
+ " -f"
74
+ when "s", "short"
75
+ " -s"
76
+ else
77
+ "ERROR"
78
+ end
79
+ end
80
+ if opt == "ERROR"
81
+ skip_resource "The `sys_info.hostname` resource is not supported with that option on your OS."
82
+ else
83
+ inspec.command("hostname#{opt}").stdout.chomp
84
+ end
85
+ end
86
+
30
87
  # returns the Manufacturer of the local system
31
88
  def manufacturer
32
89
  os = inspec.os
@@ -54,5 +111,9 @@ module Inspec::Resources
54
111
  skip_resource "The `sys_info.model` resource is not supported on your OS yet."
55
112
  end
56
113
  end
114
+
115
+ def to_s
116
+ "System Information"
117
+ end
57
118
  end
58
119
  end
@@ -0,0 +1 @@
1
+ require "inspec/resources/users"
data/lib/inspec/rule.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  # copyright: 2015, Dominik Richter
2
2
 
3
3
  require "method_source"
4
+ require "date"
4
5
  require "inspec/describe"
5
6
  require "inspec/expect"
6
7
  require "inspec/resource"
7
8
  require "inspec/resources/os"
9
+ require "inspec/input_registry"
8
10
 
9
11
  module Inspec
10
12
  class Rule
@@ -28,6 +30,7 @@ module Inspec
28
30
  @resource_dsl
29
31
  end
30
32
 
33
+ attr_reader :__waiver_data
31
34
  def initialize(id, profile_id, opts, &block)
32
35
  @impact = nil
33
36
  @title = nil
@@ -42,7 +45,7 @@ module Inspec
42
45
  @__rule_id = id
43
46
  @__profile_id = profile_id
44
47
  @__checks = []
45
- @__skip_rule = {}
48
+ @__skip_rule = {} # { result: true, message: "Why", type: [:only_if, :waiver] }
46
49
  @__merge_count = 0
47
50
  @__merge_changes = []
48
51
  @__skip_only_if_eval = opts[:skip_only_if_eval]
@@ -52,6 +55,11 @@ module Inspec
52
55
 
53
56
  begin
54
57
  instance_eval(&block)
58
+
59
+ # By applying waivers *after* the instance eval, we assure that
60
+ # waivers have higher precedence than only_if.
61
+ __apply_waivers
62
+
55
63
  rescue StandardError => e
56
64
  # We've encountered an exception while trying to eval the code inside the
57
65
  # control block. We need to prevent the exception from bubbling up, and
@@ -141,6 +149,7 @@ module Inspec
141
149
  return if @__skip_only_if_eval == true
142
150
 
143
151
  @__skip_rule[:result] ||= !yield
152
+ @__skip_rule[:type] = :only_if
144
153
  @__skip_rule[:message] = message
145
154
  end
146
155
 
@@ -193,9 +202,9 @@ module Inspec
193
202
  rule.instance_variable_get(:@__skip_rule)
194
203
  end
195
204
 
196
- def self.set_skip_rule(rule, value, message = nil)
205
+ def self.set_skip_rule(rule, value, message = nil, type = :only_if)
197
206
  rule.instance_variable_set(:@__skip_rule,
198
- { result: value, message: message })
207
+ { result: value, message: message, type: type })
199
208
  end
200
209
 
201
210
  def self.merge_count(rule)
@@ -206,14 +215,16 @@ module Inspec
206
215
  rule.instance_variable_get(:@__merge_changes)
207
216
  end
208
217
 
218
+ # If a rule is marked to be skipped, this
219
+ # creates a dummay array of "checks" with a skip outcome
209
220
  def self.prepare_checks(rule)
210
221
  skip_check = skip_status(rule)
211
222
  return checks(rule) unless skip_check[:result].eql?(true)
212
223
 
213
224
  if skip_check[:message]
214
- msg = "Skipped control due to only_if condition: #{skip_check[:message]}"
225
+ msg = "Skipped control due to #{skip_check[:type]} condition: #{skip_check[:message]}"
215
226
  else
216
- msg = "Skipped control due to only_if condition."
227
+ msg = "Skipped control due to #{skip_check[:type]} condition."
217
228
  end
218
229
 
219
230
  # TODO: we use os as the carrier here, but should consider
@@ -251,7 +262,8 @@ module Inspec
251
262
  skip_check = skip_status(src)
252
263
  sr = skip_check[:result]
253
264
  msg = skip_check[:message]
254
- set_skip_rule(dst, sr, msg) unless sr.nil?
265
+ skip_type = skip_check[:type]
266
+ set_skip_rule(dst, sr, msg, skip_type) unless sr.nil?
255
267
 
256
268
  # Save merge history
257
269
  dst.instance_variable_set(:@__merge_count, merge_count(dst) + 1)
@@ -267,6 +279,56 @@ module Inspec
267
279
  @__checks.push([describe_or_expect, values, block])
268
280
  end
269
281
 
282
+ # Look for an input with a matching ID, and if found, apply waiver
283
+ # skipping logic. Basically, if we have a current waiver, and it says
284
+ # to skip, we'll replace all the checks with a dummy check (same as
285
+ # only_if mechanism)
286
+ # Double underscore: not intended to be called as part of the DSL
287
+ def __apply_waivers
288
+ input_name = @__rule_id # TODO: control ID slugging
289
+ registry = Inspec::InputRegistry.instance
290
+ input = registry.inputs_by_profile.dig(@__profile_id, input_name)
291
+ return unless input
292
+
293
+ # An InSpec Input is a datastructure that tracks a profile parameter
294
+ # over time. Its value can be set by many sources, and it keeps a
295
+ # log of each "set" event so that when it is collapsed to a value,
296
+ # it can determine the correct (highest priority) value.
297
+ # Store in an instance variable for.. later reading???
298
+ @__waiver_data = input.value
299
+ __waiver_data["skipped_due_to_waiver"] = false
300
+ __waiver_data["message"] = ""
301
+
302
+ # Waivers should have a hash value with keys possibly including skip and
303
+ # expiration_date. We only care here if it has a skip key and it
304
+ # is yes-like, since all non-skipped waiver operations are handled
305
+ # during reporting phase.
306
+ return unless __waiver_data.key?("skip") && __waiver_data["skip"]
307
+
308
+ # OK, the intent is to skip. Does it have an expiration date, and
309
+ # if so, is it in the future?
310
+ expiry = __waiver_data["expiration_date"]
311
+ if expiry
312
+ if expiry.is_a?(Date)
313
+ # It appears that yaml.rb automagically parses dates for us
314
+ if expiry < Date.today # If the waiver expired, return - no skip applied
315
+ __waiver_data["message"] = "Waiver expired on #{expiry}, evaluating control normally"
316
+ return
317
+ end
318
+ else
319
+ ui = Inspec::UI.new
320
+ ui.error("Unable to parse waiver expiration date '#{expiry}' for control #{@__rule_id}")
321
+ ui.exit(:usage_error)
322
+ end
323
+ end
324
+
325
+ # OK, apply a skip.
326
+ @__skip_rule[:result] = true
327
+ @__skip_rule[:type] = :waiver
328
+ @__skip_rule[:message] = __waiver_data["justification"]
329
+ __waiver_data["skipped_due_to_waiver"] = true
330
+ end
331
+
270
332
  #
271
333
  # Takes a block and returns a block that will run the given block
272
334
  # with access to the resource_dsl of the current class. This is to
data/lib/inspec/runner.rb CHANGED
@@ -9,7 +9,6 @@ require "inspec/metadata"
9
9
  require "inspec/config"
10
10
  require "inspec/dependencies/cache"
11
11
  require "inspec/dist"
12
- require "inspec/resources"
13
12
  require "inspec/reporters"
14
13
  require "inspec/runner_rspec"
15
14
  # spec requirements
@@ -57,6 +56,12 @@ module Inspec
57
56
  RunnerRspec.new(@conf)
58
57
  end
59
58
 
59
+ if @conf[:waiver_file]
60
+ waivers = @conf.delete(:waiver_file)
61
+ @conf[:input_file] ||= []
62
+ @conf[:input_file].concat waivers
63
+ end
64
+
60
65
  # About reading inputs:
61
66
  # @conf gets passed around a lot, eventually to
62
67
  # Inspec::InputRegistry.register_external_inputs.
@@ -171,6 +171,7 @@ module Inspec
171
171
  metadata[:descriptions] = rule.descriptions
172
172
  metadata[:code] = rule.instance_variable_get(:@__code)
173
173
  metadata[:source_location] = rule.instance_variable_get(:@__source_location)
174
+ metadata[:waiver_data] = rule.__waiver_data
174
175
  end
175
176
  end
176
177
  end
data/lib/inspec/shell.rb CHANGED
@@ -112,6 +112,7 @@ module Inspec
112
112
  #{print_target_info}
113
113
  EOF
114
114
  elsif topic == "resources"
115
+ require "inspec/resources"
115
116
  resources.sort.each do |resource|
116
117
  puts " - #{resource}"
117
118
  end
@@ -134,7 +135,13 @@ module Inspec
134
135
  info += "https://www.inspec.io/docs/reference/resources/#{topic}\n\n"
135
136
  puts info
136
137
  else
137
- puts "The resource #{topic} does not exist. For a list of valid resources, type: help resources"
138
+ begin
139
+ require "inspec/resources/#{topic}"
140
+ help topic
141
+ rescue LoadError
142
+ # TODO: stderr!
143
+ puts "The resource #{topic} does not exist. For a list of valid resources, type: help resources"
144
+ end
138
145
  end
139
146
  end
140
147
 
@@ -1,4 +1,4 @@
1
- require "inspec/objects/input"
1
+ require "inspec/input"
2
2
 
3
3
  module PkeyReader
4
4
  def read_pkey(filecontent, passphrase)