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
@@ -1,6 +1,6 @@
1
1
  require "forwardable"
2
2
  require "singleton"
3
- require "inspec/objects/input"
3
+ require "inspec/input"
4
4
  require "inspec/secrets"
5
5
  require "inspec/exceptions"
6
6
  require "inspec/plugin/v2"
@@ -13,6 +13,15 @@ module Inspec
13
13
  include Singleton
14
14
  extend Forwardable
15
15
 
16
+ class Error < Inspec::Error; end
17
+ class ProfileLookupError < Error
18
+ attr_accessor :profile_name
19
+ end
20
+ class InputLookupError < Error
21
+ attr_accessor :profile_name
22
+ attr_accessor :input_name
23
+ end
24
+
16
25
  attr_reader :inputs_by_profile, :profile_aliases, :plugins
17
26
  def_delegator :inputs_by_profile, :each
18
27
  def_delegator :inputs_by_profile, :[]
@@ -1,5 +1,5 @@
1
1
  module Inspec
2
- autoload :Input, "inspec/objects/input"
2
+ # TODO: these should be namespaced in Objects
3
3
  autoload :Tag, "inspec/objects/tag"
4
4
  autoload :Control, "inspec/objects/control"
5
5
  autoload :Describe, "inspec/objects/describe"
@@ -10,3 +10,5 @@ module Inspec
10
10
  autoload :Test, "inspec/objects/test"
11
11
  autoload :Value, "inspec/objects/value"
12
12
  end
13
+
14
+ require "inspec/objects/input" # already defined so you can't autoload
@@ -1,307 +1,14 @@
1
- require "inspec/utils/deprecation"
1
+ require "inspec/input"
2
2
 
3
- # For backwards compatibility during the rename (see #3802),
4
- # maintain the Inspec::Attribute namespace for people checking for
5
- # Inspec::Attribute::DEFAULT_ATTRIBUTE
6
3
  module Inspec
7
- class Attribute
8
- # This only exists to create the Inspec::Attribute::DEFAULT_ATTRIBUTE symbol with a class
9
- class DEFAULT_ATTRIBUTE; end # rubocop: disable Naming/ClassAndModuleCamelCase
10
- end
11
- end
12
-
13
- module Inspec
14
- class Input
15
- #===========================================================================#
16
- # Class Input::Event
17
- #===========================================================================#
18
-
19
- # Information about how the input obtained its value.
20
- # Each time it changes, an Input::Event is added to the #events array.
21
- class Event
22
- EVENT_PROPERTIES = [
23
- :action, # :create, :set, :fetch
24
- :provider, # Name of the plugin
25
- :priority, # Priority of this plugin for resolving conflicts. 1-100, higher numbers win.
26
- :value, # New value, if provided.
27
- :file, # File containing the input-changing action, if known
28
- :line, # Line in file containing the input-changing action, if known
29
- :hit, # if action is :fetch, true if the remote source had the input
30
- ].freeze
31
-
32
- # Value has a special handler
33
- EVENT_PROPERTIES.reject { |p| p == :value }.each do |prop|
34
- attr_accessor prop
35
- end
36
-
37
- attr_reader :value
38
-
39
- def initialize(properties = {})
40
- @value_has_been_set = false
41
-
42
- properties.each do |prop_name, prop_value|
43
- if EVENT_PROPERTIES.include? prop_name
44
- # OK, save the property
45
- send((prop_name.to_s + "=").to_sym, prop_value)
46
- else
47
- raise "Unrecognized property to Input::Event: #{prop_name}"
48
- end
49
- end
50
- end
51
-
52
- def value=(the_val)
53
- # Even if set to nil or false, it has indeed been set; note that fact.
54
- @value_has_been_set = true
55
- @value = the_val
56
- end
57
-
58
- def value_has_been_set?
59
- @value_has_been_set
60
- end
61
-
62
- def diagnostic_string
63
- to_h.reject { |_, val| val.nil? }.to_a.map { |pair| "#{pair[0]}: '#{pair[1]}'" }.join(", ")
64
- end
65
-
66
- def to_h
67
- EVENT_PROPERTIES.each_with_object({}) do |prop, hash|
68
- hash[prop] = send(prop)
69
- end
70
- end
71
-
72
- def self.probe_stack
73
- frames = caller_locations(2, 40)
74
- frames.reject! { |f| f.path && f.path.include?("/lib/inspec/") }
75
- frames.first
76
- end
77
- end
78
-
79
- #===========================================================================#
80
- # Class NO_VALUE_SET
81
- #===========================================================================#
82
- # This special class is used to represent the value when an input has
83
- # not been assigned a value. This allows a user to explicitly assign nil
84
- # to an input.
85
- class NO_VALUE_SET # rubocop: disable Naming/ClassAndModuleCamelCase
86
- def initialize(name)
87
- @name = name
88
-
89
- # output warn message if we are in a exec call
90
- if Inspec::BaseCLI.inspec_cli_command == :exec
91
- Inspec::Log.warn(
92
- "Input '#{@name}' does not have a value. "\
93
- "Use --input-file to provide a value for '#{@name}' or specify a "\
94
- "value with `attribute('#{@name}', value: 'somevalue', ...)`."
95
- )
96
- end
97
- end
98
-
99
- def method_missing(*_)
100
- self
101
- end
102
-
103
- def respond_to_missing?(_, _)
104
- true
105
- end
106
-
107
- def to_s
108
- "Input '#{@name}' does not have a value. Skipping test."
109
- end
110
-
111
- def is_a?(klass)
112
- if klass == Inspec::Attribute::DEFAULT_ATTRIBUTE
113
- Inspec.deprecate(:rename_attributes_to_inputs, "Don't check for `is_a?(Inspec::Attribute::DEFAULT_ATTRIBUTE)`, check for `Inspec::Input::NO_VALUE_SET")
114
- true # lie for backward compatibility
115
- else
116
- super(klass)
117
- end
118
- end
119
4
 
120
- def kind_of?(klass)
121
- if klass == Inspec::Attribute::DEFAULT_ATTRIBUTE
122
- Inspec.deprecate(:rename_attributes_to_inputs, "Don't check for `kind_of?(Inspec::Attribute::DEFAULT_ATTRIBUTE)`, check for `Inspec::Input::NO_VALUE_SET")
123
- true # lie for backward compatibility
124
- else
125
- super(klass)
126
- end
127
- end
128
- end
129
- end
5
+ # NOTE: due to namespacing, this reopens and extends the existing
6
+ # Inspec::Input. This should be under Inspec::Objects but that ship
7
+ # has sailed.
130
8
 
131
9
  class Input
132
- #===========================================================================#
133
- # Class Inspec::Input
134
- #===========================================================================#
135
-
136
- # Validation types for input values
137
- VALID_TYPES = %w{
138
- String
139
- Numeric
140
- Regexp
141
- Array
142
- Hash
143
- Boolean
144
- Any
145
- }.freeze
146
-
147
- # If you call `input` in a control file, the input will receive this priority.
148
- # You can override that with a :priority option.
149
- DEFAULT_PRIORITY_FOR_DSL_ATTRIBUTES = 20
150
-
151
- # If you somehow manage to initialize an Input outside of the DSL,
152
- # AND you don't provide an Input::Event, this is the priority you get.
153
- DEFAULT_PRIORITY_FOR_UNKNOWN_CALLER = 10
154
-
155
- # If you directly call value=, this is the priority assigned.
156
- # This is the highest priority within InSpec core; though plugins
157
- # are free to go higher.
158
- DEFAULT_PRIORITY_FOR_VALUE_SET = 60
159
10
 
160
- attr_reader :description, :events, :identifier, :name, :required, :title, :type
161
-
162
- def initialize(name, options = {})
163
- @name = name
164
- @opts = options
165
- if @opts.key?(:default)
166
- Inspec.deprecate(:attrs_value_replaces_default, "input name: '#{name}'")
167
- if @opts.key?(:value)
168
- Inspec::Log.warn "Input #{@name} created using both :default and :value options - ignoring :default"
169
- @opts.delete(:default)
170
- end
171
- end
172
-
173
- # Array of Input::Event objects. These compete with one another to determine
174
- # the value of the input when value() is called, as well as providing a
175
- # debugging record of when and how the value changed.
176
- @events = []
177
- events.push make_creation_event(options)
178
-
179
- update(options)
180
- end
181
-
182
- def set_events
183
- events.select { |e| e.action == :set }
184
- end
185
-
186
- def diagnostic_string
187
- "Input #{name}, with history:\n" +
188
- events.map(&:diagnostic_string).map { |line| " #{line}" }.join("\n")
189
- end
190
-
191
- #--------------------------------------------------------------------------#
192
- # Managing Value
193
- #--------------------------------------------------------------------------#
194
-
195
- def update(options)
196
- _update_set_metadata(options)
197
- normalize_type_restriction!
198
-
199
- # Values are set by passing events in; but we can also infer an event.
200
- if options.key?(:value) || options.key?(:default)
201
- if options.key?(:event)
202
- if options.key?(:value) || options.key?(:default)
203
- Inspec::Log.warn "Do not provide both an Event and a value as an option to attribute('#{name}') - using value from event"
204
- end
205
- else
206
- self.class.infer_event(options) # Sets options[:event]
207
- end
208
- end
209
- events << options[:event] if options.key? :event
210
-
211
- enforce_type_restriction!
212
- end
213
-
214
- # We can determine a value:
215
- # 1. By event.value (preferred)
216
- # 2. By options[:value]
217
- # 3. By options[:default] (deprecated)
218
- def self.infer_event(options)
219
- # Don't rely on this working; you really should be passing a proper Input::Event
220
- # with the context information you have.
221
- location = Input::Event.probe_stack
222
- event = Input::Event.new(
223
- action: :set,
224
- provider: options[:provider] || :unknown,
225
- priority: options[:priority] || Inspec::Input::DEFAULT_PRIORITY_FOR_UNKNOWN_CALLER,
226
- file: location.path,
227
- line: location.lineno
228
- )
229
-
230
- if options.key?(:default)
231
- Inspec.deprecate(:attrs_value_replaces_default, "attribute name: '#{name}'")
232
- if options.key?(:value)
233
- Inspec::Log.warn "Input #{@name} created using both :default and :value options - ignoring :default"
234
- options.delete(:default)
235
- else
236
- options[:value] = options.delete(:default)
237
- end
238
- end
239
- event.value = options[:value] if options.key?(:value)
240
- options[:event] = event
241
- end
242
-
243
- private
244
-
245
- def _update_set_metadata(options)
246
- # Basic metadata
247
- @title = options[:title] if options.key?(:title)
248
- @description = options[:description] if options.key?(:description)
249
- @required = options[:required] if options.key?(:required)
250
- @identifier = options[:identifier] if options.key?(:identifier) # TODO: determine if this is ever used
251
- @type = options[:type] if options.key?(:type)
252
- end
253
-
254
- def make_creation_event(options)
255
- loc = options[:location] || Event.probe_stack
256
- Input::Event.new(
257
- action: :create,
258
- provider: options[:provider],
259
- file: loc.path,
260
- line: loc.lineno
261
- )
262
- end
263
-
264
- # Determine the current winning value, but don't validate it
265
- def current_value
266
- # Examine the events to determine highest-priority value. Tie-break
267
- # by using the last one set.
268
- events_that_set_a_value = events.select(&:value_has_been_set?)
269
- winning_priority = events_that_set_a_value.map(&:priority).max
270
- winning_events = events_that_set_a_value.select { |e| e.priority == winning_priority }
271
- winning_event = winning_events.last # Last for tie-break
272
-
273
- if winning_event.nil?
274
- # No value has been set - return special no value object
275
- NO_VALUE_SET.new(name)
276
- else
277
- winning_event.value # May still be nil
278
- end
279
- end
280
-
281
- public
282
-
283
- def value=(new_value, priority = DEFAULT_PRIORITY_FOR_VALUE_SET)
284
- # Inject a new Event with the new value.
285
- location = Event.probe_stack
286
- events << Event.new(
287
- action: :set,
288
- provider: :value_setter,
289
- priority: priority,
290
- value: new_value,
291
- file: location.path,
292
- line: location.lineno
293
- )
294
- enforce_type_restriction!
295
- end
296
-
297
- def value
298
- enforce_required_validation!
299
- current_value
300
- end
301
-
302
- def has_value?
303
- !current_value.is_a? NO_VALUE_SET
304
- end
11
+ # NOTE: No initialize method or accessors for the reasons listed above
305
12
 
306
13
  #--------------------------------------------------------------------------#
307
14
  # Marshalling
@@ -334,94 +41,5 @@ module Inspec
334
41
  res.push "})"
335
42
  res.join("\n")
336
43
  end
337
-
338
- #--------------------------------------------------------------------------#
339
- # Value Type Coercion
340
- #--------------------------------------------------------------------------#
341
-
342
- def to_s
343
- "Input #{name} with #{current_value}"
344
- end
345
-
346
- #--------------------------------------------------------------------------#
347
- # Validation
348
- #--------------------------------------------------------------------------#
349
-
350
- private
351
-
352
- def enforce_required_validation!
353
- return unless required
354
- # skip if we are not doing an exec call (archive/vendor/check)
355
- return unless Inspec::BaseCLI.inspec_cli_command == :exec
356
-
357
- proposed_value = current_value
358
- if proposed_value.nil? || proposed_value.is_a?(NO_VALUE_SET)
359
- error = Inspec::Input::RequiredError.new
360
- error.input_name = name
361
- raise error, "Input '#{error.input_name}' is required and does not have a value."
362
- end
363
- end
364
-
365
- def enforce_type_restriction! # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
366
- return unless type
367
- return unless has_value?
368
-
369
- type_req = type
370
- return if type_req == "Any"
371
-
372
- proposed_value = current_value
373
-
374
- invalid_type = false
375
- if type_req == "Regexp"
376
- invalid_type = true unless valid_regexp?(proposed_value)
377
- elsif type_req == "Numeric"
378
- invalid_type = true unless valid_numeric?(proposed_value)
379
- elsif type_req == "Boolean"
380
- invalid_type = true unless [true, false].include?(proposed_value)
381
- elsif proposed_value.is_a?(Module.const_get(type_req)) == false
382
- # TODO: why is this case here?
383
- invalid_type = true
384
- end
385
-
386
- if invalid_type == true
387
- error = Inspec::Input::ValidationError.new
388
- error.input_name = @name
389
- error.input_value = proposed_value
390
- error.input_type = type_req
391
- raise error, "Input '#{error.input_name}' with value '#{error.input_value}' does not validate to type '#{error.input_type}'."
392
- end
393
- end
394
-
395
- def normalize_type_restriction!
396
- return unless type
397
-
398
- type_req = type.capitalize
399
- abbreviations = {
400
- "Num" => "Numeric",
401
- "Regex" => "Regexp",
402
- }
403
- type_req = abbreviations[type_req] if abbreviations.key?(type_req)
404
- unless VALID_TYPES.include?(type_req)
405
- error = Inspec::Input::TypeError.new
406
- error.input_type = type_req
407
- raise error, "Type '#{error.input_type}' is not a valid input type."
408
- end
409
- @type = type_req
410
- end
411
-
412
- def valid_numeric?(value)
413
- Float(value)
414
- true
415
- rescue
416
- false
417
- end
418
-
419
- def valid_regexp?(value)
420
- # check for invalid regex syntax
421
- Regexp.new(value)
422
- true
423
- rescue
424
- false
425
- end
426
44
  end
427
45
  end
@@ -15,7 +15,7 @@ module Inspec
15
15
  end
16
16
 
17
17
  def to_ruby
18
- "tag #{key.inspect}: #{value.inspect}"
18
+ "tag #{key}: #{value.inspect}"
19
19
  end
20
20
 
21
21
  def to_s