inspec 4.12.0 → 4.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72aac2ba7eb1565ecbbd18436e96c770151f5be68b3bcc8897db7a0baee22621
4
- data.tar.gz: 2466be933623846f985f9b66e3a572d617f516274ad2dc7b81d048b2ebe2d104
3
+ metadata.gz: 877fb588106fac603caf212f8224901fbad9d694097718b85382c40140989477
4
+ data.tar.gz: afc82cd0c4e59ddf7466c68f8a67a79288548f44fddb5b49b27726f8dbe2f05e
5
5
  SHA512:
6
- metadata.gz: 269ccd7e103663bec2dd23058dc0481775b840f22a93a3cd81e8f8e0e5c774def3cd6ecaae12604f2c13fe6bcfd33864040acfa9a2ff3bf49e50c1111f5d7be9
7
- data.tar.gz: 9f6b41d84be75a69cb1c405302f7c076f6192f16f506d8d4b958bf76e707057961a6ba692dadb528b39997140c1682ee9d2d328efd8d06f2249f104497c79b62
6
+ metadata.gz: 197367b25a88684493e27d1db0b070c2790d9373476f98b09fb11a5d213d50e4c971d26c9be8c4969833cbb87f09fa28c88be448303646bd26ca85374182af44
7
+ data.tar.gz: ac3787f1a29e222221edbeefa46c620fc59d2a6e712e2c1de8d3594de448473acd0700ba7880c1d2e82bd7b02804f161ad42421423940f36b8ffa8b43b0b90d1
@@ -25,10 +25,6 @@
25
25
  "plugin_name": "inspec-release",
26
26
  "rationale": "This gem is currently only a placeholder, waiting to be built."
27
27
  },
28
- {
29
- "plugin_name": "inspec-vault",
30
- "rationale": "This gem is currently only a placeholder, waiting to be built."
31
- },
32
28
  {
33
29
  "plugin_name": "train-vault",
34
30
  "rationale": "This gem is currently only a placeholder, waiting to be built."
data/lib/fetchers/git.rb CHANGED
@@ -52,6 +52,7 @@ module Fetchers
52
52
  # processing, but then again, if you passed a relative path
53
53
  # to an on-disk repo, you probably expect it to exist.
54
54
  return url_or_file_path unless File.exist?(url_or_file_path)
55
+
55
56
  # It's important to expand this path, because it may be specified
56
57
  # locally in the metadata files, and when we clone, we will be
57
58
  # in a temp dir.
@@ -97,6 +98,7 @@ module Fetchers
97
98
 
98
99
  def cache_key
99
100
  return resolved_ref unless @relative_path
101
+
100
102
  OpenSSL::Digest::SHA256.hexdigest(resolved_ref + @relative_path)
101
103
  end
102
104
 
@@ -133,6 +133,8 @@ module Inspec
133
133
  option :reporter, type: :array,
134
134
  banner: "one two:/output/file/path",
135
135
  desc: "Enable one or more output reporters: cli, documentation, html, progress, json, json-min, json-rspec, junit, yaml"
136
+ option :input, type: :array, banner: "name1=value1 name2=value2",
137
+ desc: "Specify one or more inputs directly on the command line, as --input NAME=VALUE"
136
138
  option :input_file, type: :array,
137
139
  desc: "Load one or more input files, a YAML file with values for the profile to use"
138
140
  option :attrs, type: :array,
data/lib/inspec/cli.rb CHANGED
@@ -124,8 +124,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
124
124
  else
125
125
  %w{location profile controls timestamp valid}.each do |item|
126
126
  prepared_string = format("%-12s %s",
127
- "#{item.to_s.capitalize} :",
128
- result[:summary][item.to_sym])
127
+ "#{item.to_s.capitalize} :",
128
+ result[:summary][item.to_sym])
129
129
  ui.plain_line(prepared_string)
130
130
  end
131
131
  puts
data/lib/inspec/config.rb CHANGED
@@ -7,9 +7,12 @@ require "forwardable"
7
7
  require "thor"
8
8
  require "base64"
9
9
  require "inspec/base_cli"
10
+ require "inspec/plugin/v2/filter"
10
11
 
11
12
  module Inspec
12
13
  class Config
14
+ include Inspec::Plugin::V2::FilterPredicates
15
+
13
16
  # These are options that apply to any transport
14
17
  GENERIC_CREDENTIALS = %w{
15
18
  backend
@@ -23,6 +26,11 @@ module Inspec
23
26
  shell_command
24
27
  }.freeze
25
28
 
29
+ KNOWN_VERSIONS = [
30
+ "1.1",
31
+ "1.2",
32
+ ].freeze
33
+
26
34
  extend Forwardable
27
35
 
28
36
  # Many parts of InSpec expect to treat the Config as a Hash
@@ -48,6 +56,7 @@ module Inspec
48
56
  def initialize(cli_opts = {}, cfg_io = nil, command_name = nil)
49
57
  @command_name = command_name || (ARGV.empty? ? nil : ARGV[0].to_sym)
50
58
  @defaults = Defaults.for_command(@command_name)
59
+ @plugin_cfg = {}
51
60
 
52
61
  @cli_opts = cli_opts.dup
53
62
  cfg_io = resolve_cfg_io(@cli_opts, cfg_io)
@@ -119,6 +128,13 @@ module Inspec
119
128
  end
120
129
  end
121
130
 
131
+ #-----------------------------------------------------------------------#
132
+ # Fetching Plugin Data
133
+ #-----------------------------------------------------------------------#
134
+ def fetch_plugin_config(plugin_name)
135
+ Thor::CoreExt::HashWithIndifferentAccess.new(@plugin_cfg[plugin_name] || {})
136
+ end
137
+
122
138
  private
123
139
 
124
140
  def _utc_merge_transport_options(credentials, transport_name)
@@ -285,16 +301,24 @@ module Inspec
285
301
  # Assume legacy format, which is unconstrained
286
302
  return unless version
287
303
 
288
- unless version == "1.1"
289
- raise Inspec::ConfigError::Invalid, "Unsupported config file version '#{version}' - currently supported versions: 1.1"
304
+ unless KNOWN_VERSIONS.include?(version)
305
+ raise Inspec::ConfigError::Invalid, "Unsupported config file version '#{version}' - currently supported versions: #{KNOWN_VERSIONS.join(",")}"
290
306
  end
291
307
 
308
+ # Use Gem::Version for comparision operators
309
+ cfg_version = Gem::Version.new(version)
310
+ version_1_2 = Gem::Version.new("1.2")
311
+
312
+ # TODO: proper schema version loading and validation
292
313
  valid_fields = %w{version cli_options credentials compliance reporter}.sort
314
+ valid_fields << "plugins" if cfg_version >= version_1_2
293
315
  @cfg_file_contents.keys.each do |seen_field|
294
316
  unless valid_fields.include?(seen_field)
295
317
  raise Inspec::ConfigError::Invalid, "Unrecognized top-level configuration field #{seen_field}. Recognized fields: #{valid_fields.join(", ")}"
296
318
  end
297
319
  end
320
+
321
+ validate_plugins! if cfg_version >= version_1_2
298
322
  end
299
323
 
300
324
  def validate_reporters!(reporters)
@@ -334,6 +358,29 @@ module Inspec
334
358
  raise ArgumentError, "The option --reporter can only have a single report outputting to stdout." if stdout_reporters > 1
335
359
  end
336
360
 
361
+ def validate_plugins!
362
+ return unless @cfg_file_contents.key? "plugins"
363
+
364
+ data = @cfg_file_contents["plugins"]
365
+ unless data.is_a?(Hash)
366
+ raise Inspec::ConfigError::Invalid, "The 'plugin' field in your config file must be a hash (key-value list), not an array."
367
+ end
368
+
369
+ data.each do |plugin_name, plugin_settings|
370
+ # Enforce that every key is a valid inspec or train plugin name
371
+ unless valid_plugin_name?(plugin_name)
372
+ raise Inspec::ConfigError::Invalid, "Plugin settings should ne named after the the InSpec or Train plugin. Valid names must begin with inspec- or train-, not '#{plugin_name}' "
373
+ end
374
+
375
+ # Enforce that every entry is hash-valued
376
+ unless plugin_settings.is_a?(Hash)
377
+ raise Inspec::ConfigError::Invalid, "The plugin settings for '#{plugin_name}' in your config file should be a Hash (key-value list)."
378
+ end
379
+ end
380
+
381
+ @plugin_cfg = data
382
+ end
383
+
337
384
  #-----------------------------------------------------------------------#
338
385
  # Merging Options
339
386
  #-----------------------------------------------------------------------#
@@ -64,6 +64,9 @@ module Inspec
64
64
  #-------------------------------------------------------------#
65
65
 
66
66
  def find_or_register_input(input_name, profile_name, options = {})
67
+ input_name = input_name.to_s
68
+ profile_name = profile_name.to_s
69
+
67
70
  if profile_alias?(profile_name) && !profile_aliases[profile_name].nil?
68
71
  alias_name = profile_name
69
72
  profile_name = profile_aliases[profile_name]
@@ -132,10 +135,37 @@ module Inspec
132
135
  bind_inputs_from_metadata(profile_name, sources[:profile_metadata])
133
136
  bind_inputs_from_input_files(profile_name, sources[:cli_input_files])
134
137
  bind_inputs_from_runner_api(profile_name, sources[:runner_api])
138
+ bind_inputs_from_cli_args(profile_name, sources[:cli_input_arg])
135
139
  end
136
140
 
137
141
  private
138
142
 
143
+ def bind_inputs_from_cli_args(profile_name, input_list)
144
+ # TODO: move this into a core plugin
145
+
146
+ return if input_list.nil?
147
+ return if input_list.empty?
148
+
149
+ # These arrive as an array of "name=value" strings
150
+ # If the user used a comma, we'll see unfortunately see it as "name=value," pairs
151
+ input_list.each do |pair|
152
+ unless pair.include?("=")
153
+ if pair.end_with?(".yaml")
154
+ raise ArgumentError, "ERROR: --input is used for individual input values, as --input name=value. Use --input-file to load a YAML file."
155
+ else
156
+ raise ArgumentError, "ERROR: An '=' is required when using --input. Usage: --input input_name1=input_value1 input2=value2"
157
+ end
158
+ end
159
+ input_name, input_value = pair.split("=")
160
+ evt = Inspec::Input::Event.new(
161
+ value: input_value.chomp(","), # Trim trailing comma if any
162
+ provider: :cli,
163
+ priority: 50
164
+ )
165
+ find_or_register_input(input_name, profile_name, event: evt)
166
+ end
167
+ end
168
+
139
169
  def bind_inputs_from_runner_api(profile_name, input_hash)
140
170
  # TODO: move this into a core plugin
141
171
 
@@ -88,6 +88,10 @@ module Inspec
88
88
  errors.push("Version needs to be in SemVer format")
89
89
  end
90
90
 
91
+ unless supports_runtime?
92
+ warnings.push("The current inspec version #{Inspec::VERSION} cannot satisfy profile inspec_version constraint #{params[:inspec_version]}")
93
+ end
94
+
91
95
  %w{title summary maintainer copyright license}.each do |field|
92
96
  next unless params[field.to_sym].nil?
93
97
 
@@ -81,17 +81,13 @@ module Inspec
81
81
  @resource_skipped = false
82
82
  @resource_failed = false
83
83
  @supports = Inspec::Resource.supports[name]
84
+ @resource_exception_message = nil
84
85
 
85
86
  # attach the backend to this instance
86
87
  @__backend_runner__ = backend
87
88
  @__resource_name__ = name
88
89
 
89
- # check resource supports
90
- supported = true
91
- supported = check_supports unless @supports.nil?
92
- test_backend = defined?(Train::Transports::Mock::Connection) && backend.backend.class == Train::Transports::Mock::Connection
93
- # do not return if we are supported, or for tests
94
- return unless supported || test_backend
90
+ check_supports unless @supports.nil? # this has side effects
95
91
 
96
92
  # call the resource initializer
97
93
  begin
@@ -100,12 +96,10 @@ module Inspec
100
96
  skip_resource(e.message)
101
97
  rescue Inspec::Exceptions::ResourceFailed => e
102
98
  fail_resource(e.message)
99
+ rescue NotImplementedError => e
100
+ fail_resource(e.message) unless @resource_failed
103
101
  rescue NoMethodError => e
104
- # The new platform resources have methods generated on the fly
105
- # for inspec check to work we need to skip these train errors
106
- raise unless test_backend && e.receiver.class == Train::Transports::Mock::Connection
107
-
108
- skip_resource(e.message)
102
+ skip_resource(e.message) unless @resource_failed
109
103
  end
110
104
  end
111
105
 
@@ -2,6 +2,8 @@ require "singleton"
2
2
  require "json"
3
3
  require "inspec/globals"
4
4
 
5
+ module Inspec::Plugin; end
6
+
5
7
  module Inspec::Plugin::V2
6
8
  Exclusion = Struct.new(:plugin_name, :rationale)
7
9
 
@@ -60,4 +62,35 @@ module Inspec::Plugin::V2
60
62
  end
61
63
  end
62
64
  end
65
+
66
+ # To be a valid plugin name, the plugin must beign with either
67
+ # inspec- or train-, AND ALSO not be on the exclusion list.
68
+ # We maintain this exclusion list to avoid confusing users.
69
+ # For example, we want to have a real gem named inspec-test-fixture,
70
+ # but we don't want the users to see that.
71
+ module FilterPredicates
72
+ def train_plugin_name?(name)
73
+ valid_plugin_name?(name, :train)
74
+ end
75
+
76
+ def inspec_plugin_name?(name)
77
+ valid_plugin_name?(name, :inspec)
78
+ end
79
+
80
+ def valid_plugin_name?(name, kind = :either)
81
+ # Must have a permitted prefix.
82
+ return false unless case kind
83
+ when :inspec
84
+ name.to_s.start_with?("inspec-")
85
+ when :train
86
+ name.to_s.start_with?("train-")
87
+ when :either
88
+ name.to_s.match(/^(inspec|train)-/)
89
+ else false
90
+ end # rubocop: disable Layout/EndAlignment
91
+
92
+ # And must not be on the exclusion list.
93
+ ! Inspec::Plugin::V2::PluginFilter.exclude?(name)
94
+ end
95
+ end
63
96
  end
@@ -60,14 +60,15 @@ module Inspec::Plugin::V2
60
60
  # TODO: - check plugins.json for validity before trying anything that needs to modify it.
61
61
  validate_installation_opts(plugin_name, opts)
62
62
 
63
- # TODO: change all of these to return installed spec/gem/thingy
64
63
  # TODO: return installed thingy
65
64
  if opts[:path]
66
65
  install_from_path(plugin_name, opts)
67
66
  elsif opts[:gem_file]
68
- install_from_gem_file(plugin_name, opts)
67
+ gem_version = install_from_gem_file(plugin_name, opts)
68
+ opts[:version] = gem_version.to_s
69
69
  else
70
- install_from_remote_gems(plugin_name, opts)
70
+ gem_version = install_from_remote_gems(plugin_name, opts)
71
+ opts[:version] = gem_version.to_s
71
72
  end
72
73
 
73
74
  update_plugin_config_file(plugin_name, opts.merge({ action: :install }))
@@ -88,9 +89,9 @@ module Inspec::Plugin::V2
88
89
 
89
90
  # TODO: Handle installing from a local file
90
91
  # TODO: Perform dependency checks to make sure the new solution is valid
91
- install_from_remote_gems(plugin_name, opts)
92
+ gem_version = install_from_remote_gems(plugin_name, opts)
92
93
 
93
- update_plugin_config_file(plugin_name, opts.merge({ action: :update }))
94
+ update_plugin_config_file(plugin_name, opts.merge({ action: :update, version: gem_version.to_s }))
94
95
  end
95
96
 
96
97
  # Uninstalls (removes) a plugin. Refers to plugin.json to determine if it
@@ -335,13 +336,15 @@ module Inspec::Plugin::V2
335
336
  # not obliged to during packaging.)
336
337
  # So, after each install, run a scan for all gem(specs) we manage, and copy in their gemspec file
337
338
  # into the exploded gem source area if absent.
338
-
339
339
  loader.list_managed_gems.each do |spec|
340
340
  path_inside_source = File.join(spec.gem_dir, "#{spec.name}.gemspec")
341
341
  unless File.exist?(path_inside_source)
342
342
  File.write(path_inside_source, spec.to_ruby)
343
343
  end
344
344
  end
345
+
346
+ # Locate the GemVersion for the new dependency and return it
347
+ solution.detect { |g| g.name == new_plugin_dependency.name }.version
345
348
  end
346
349
 
347
350
  #===================================================================#
@@ -365,7 +368,7 @@ module Inspec::Plugin::V2
365
368
  # excluding any that are path-or-core-based, excluding the gem to be removed
366
369
  plugin_deps_we_still_must_satisfy = registry.plugin_statuses
367
370
  plugin_deps_we_still_must_satisfy = plugin_deps_we_still_must_satisfy.select do |status|
368
- status.installation_type == :gem && status.name != plugin_name_to_be_removed.to_sym
371
+ status.installation_type == :user_gem && status.name != plugin_name_to_be_removed.to_sym
369
372
  end
370
373
  plugin_deps_we_still_must_satisfy = plugin_deps_we_still_must_satisfy.map do |status|
371
374
  constraint = status.version || "> 0"
@@ -1,5 +1,6 @@
1
1
  require "inspec/log"
2
2
  require "inspec/plugin/v2/config_file"
3
+ require "inspec/plugin/v2/filter"
3
4
 
4
5
  # Add the current directory of the process to the load path
5
6
  $LOAD_PATH.unshift(".") unless $LOAD_PATH.include?(".")
@@ -11,9 +12,16 @@ module Inspec::Plugin::V2
11
12
  class Loader
12
13
  attr_reader :conf_file, :registry, :options
13
14
 
15
+ # For {inspec|train}_plugin_name?
16
+ include Inspec::Plugin::V2::FilterPredicates
17
+ extend Inspec::Plugin::V2::FilterPredicates
18
+
14
19
  def initialize(options = {})
15
20
  @options = options
16
21
  @registry = Inspec::Plugin::V2::Registry.instance
22
+
23
+ # User plugins are those installed by the user via `inspec plugin install`
24
+ # and are installed under ~/.inspec/gems
17
25
  unless options[:omit_user_plugins]
18
26
  @conf_file = Inspec::Plugin::V2::ConfigFile.new
19
27
  read_conf_file_into_registry
@@ -27,9 +35,8 @@ module Inspec::Plugin::V2
27
35
  # and may be safely loaded
28
36
  detect_core_plugins unless options[:omit_core_plugins]
29
37
 
30
- # Train plugins aren't InSpec plugins (they don't use our API)
31
- # but InSpec CLI manages them. So, we have to wrap them a bit.
32
- accommodate_train_plugins
38
+ # Identify plugins that inspec is co-installed with
39
+ detect_system_plugins unless options[:omit_sys_plugins]
33
40
  end
34
41
 
35
42
  def load_all
@@ -46,7 +53,7 @@ module Inspec::Plugin::V2
46
53
  begin
47
54
  # We could use require, but under testing, we need to repeatedly reload the same
48
55
  # plugin. However, gems only work with require (rubygems dooes not overload `load`)
49
- if plugin_details.installation_type == :gem
56
+ if plugin_details.installation_type == :user_gem
50
57
  activate_managed_gems_for_plugin(plugin_name)
51
58
  require plugin_details.entry_point
52
59
  else
@@ -130,10 +137,11 @@ module Inspec::Plugin::V2
130
137
  end
131
138
 
132
139
  # Lists all plugin gems found in the plugin_gem_path.
133
- # This is simply all gems that begin with train- or inspec-.
140
+ # This is simply all gems that begin with train- or inspec-
141
+ # and are not on the exclusion list.
134
142
  # @return [Array[Gem::Specification]] Specs of all gems found.
135
143
  def self.list_installed_plugin_gems
136
- list_managed_gems.select { |spec| spec.name.match(/^(inspec|train)-/) }
144
+ list_managed_gems.select { |spec| valid_plugin_name?(spec.name) }
137
145
  end
138
146
 
139
147
  def list_installed_plugin_gems
@@ -234,34 +242,70 @@ module Inspec::Plugin::V2
234
242
  end
235
243
  end
236
244
 
237
- def accommodate_train_plugins
238
- registry.plugin_names.map(&:to_s).grep(/^train-/).each do |train_plugin_name|
239
- status = registry[train_plugin_name.to_sym]
240
- status.api_generation = :'train-1'
241
-
242
- if status.installation_type == :gem
243
- # Activate the gem. This allows train to 'require' the gem later.
244
- activate_managed_gems_for_plugin(train_plugin_name)
245
- end
246
- end
247
- end
248
-
249
245
  def read_conf_file_into_registry
250
246
  conf_file.each do |plugin_entry|
251
247
  status = Inspec::Plugin::V2::Status.new
252
248
  status.name = plugin_entry[:name]
253
249
  status.loaded = false
254
- status.installation_type = (plugin_entry[:installation_type] || :gem)
250
+ status.installation_type = (plugin_entry[:installation_type] || :user_gem)
255
251
  case status.installation_type
256
- when :gem
252
+ when :user_gem
257
253
  status.entry_point = status.name.to_s
258
254
  status.version = plugin_entry[:version]
259
255
  when :path
260
256
  status.entry_point = plugin_entry[:installation_path]
261
257
  end
262
258
 
259
+ # Train plugins are not true InSpec plugins; we need to decorate them a
260
+ # bit more to integrate them.
261
+ fixup_train_plugin_status(status) if train_plugin_name?(plugin_entry[:name])
262
+
263
263
  registry[status.name] = status
264
264
  end
265
265
  end
266
+
267
+ def fixup_train_plugin_status(status)
268
+ status.api_generation = :'train-1'
269
+ if status.installation_type == :user_gem
270
+ # Activate the gem. This allows train to 'require' the gem later.
271
+ activate_managed_gems_for_plugin(status.entry_point)
272
+ end
273
+ end
274
+
275
+ def detect_system_plugins
276
+ # Find the gemspec for inspec
277
+ inspec_gemspec = Gem::Specification.find_by_name("inspec", "=#{Inspec::VERSION}")
278
+
279
+ # Make a RequestSet that represents the dependencies of inspec
280
+ inspec_deps_request_set = Gem::RequestSet.new(*inspec_gemspec.dependencies)
281
+ inspec_deps_request_set.remote = false
282
+
283
+ # Resolve the request against the installed gem universe
284
+ gem_resolver = Gem::Resolver::CurrentSet.new
285
+ runtime_solution = inspec_deps_request_set.resolve(gem_resolver)
286
+
287
+ inspec_gemspec.dependencies.each do |inspec_dep|
288
+ next unless inspec_plugin_name?(inspec_dep.name) || train_plugin_name?(inspec_dep.name)
289
+
290
+ plugin_spec = runtime_solution.detect { |s| s.name == inspec_dep.name }.spec
291
+
292
+ status = Inspec::Plugin::V2::Status.new
293
+ status.name = inspec_dep.name
294
+ status.entry_point = inspec_dep.name # gem-based, just 'require' the name
295
+ status.version = plugin_spec.version.to_s
296
+ status.loaded = false
297
+ status.installation_type = :system_gem
298
+
299
+ if train_plugin_name?(status[:name])
300
+ # Train plugins are not true InSpec plugins; we need to decorate them a
301
+ # bit more to integrate them.
302
+ fixup_train_plugin_status(status)
303
+ else
304
+ status.api_generation = 2
305
+ end
306
+
307
+ registry[status.name.to_sym] = status
308
+ end
309
+ end
266
310
  end
267
311
  end