inspec-core 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: 873c4c83c46030d3a3adb7093f085650f8400c6bbfc7c87984ebc77305aa660b
4
- data.tar.gz: '091ccf8b261dfeafac84a771ec549e46f015dcc9e5da9836982788df40c456c1'
3
+ metadata.gz: 3ff3612b0be834f75c0bd4e457652ee6736aaff5787519e28c8733c114ecc330
4
+ data.tar.gz: 58b2c9c7ddcf0bd09ce2e4d9cdf34e67ca0786cb3b97453253e375ec3fe6c29d
5
5
  SHA512:
6
- metadata.gz: ed9c35b0c96d17f96eea08658b251812ac3f03c4912a1663b9d5de600a5d6637460bf223ca90e9ca84625298b5dce64f999ac8d019c2b615b621247c48e1693a
7
- data.tar.gz: fca132a2f7221c41c78bf7379ccf74e4c07cc412d36da10817d99adc15a9c2e5a049f6311a8c6bf323a8c7deffed18462d8fb660b0c9669d5182b477cf3ca585
6
+ metadata.gz: d23d441a6db5edc44f754717a6e86b581065b8739d8ee68c657055f2ab053fcbecaed2b800938840a5d7fd8fb9879c27076862e9875258ae9c091778f5b8d99a
7
+ data.tar.gz: 3edba9eccd97749bb2661f1ab9dde985fbba0f3524e349383967d8e6517df36adad8969be7a7129e05bd96cdd35a49516c9b754fdc92ac3840630f135b1f05fb
@@ -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
@@ -116,9 +116,19 @@ module Inspec
116
116
  # we can create any inputs that were provided by various mechanisms.
117
117
  options[:runner_conf] ||= Inspec::Config.cached
118
118
 
119
+ # Catch legacy CLI input option usage
119
120
  if options[:runner_conf].key?(:attrs)
120
121
  Inspec.deprecate(:rename_attributes_to_inputs, "Use --input-file on the command line instead of --attrs.")
121
122
  options[:runner_conf][:input_file] = options[:runner_conf].delete(:attrs)
123
+ elsif options[:runner_conf].key?(:input_files)
124
+ # The kitchen-inspec docs say to use plural. Our CLI and internal expectations are singular.
125
+ options[:runner_conf][:input_file] = options[:runner_conf].delete(:input_files)
126
+ end
127
+
128
+ # Catch legacy kitchen-inspec input usage
129
+ if options[:runner_conf].key?(:attributes)
130
+ Inspec.deprecate(:rename_attributes_to_inputs, "Use :inputs in your kitchen.yml verifier config instead of :attributes.")
131
+ options[:runner_conf][:inputs] = options[:runner_conf].delete(:attributes)
122
132
  end
123
133
 
124
134
  Inspec::InputRegistry.bind_profile_inputs(
@@ -127,8 +137,8 @@ module Inspec
127
137
  # Remaining args are possible sources of inputs
128
138
  cli_input_files: options[:runner_conf][:input_file], # From CLI --input-file
129
139
  profile_metadata: metadata,
130
- # TODO: deprecation checks here
131
- runner_api: options[:runner_conf][:attributes] # This is the route the audit_cookbook and kitchen-inspec take
140
+ runner_api: options[:runner_conf][:inputs], # This is the route the audit_cookbook and kitchen-inspec take
141
+ cli_input_arg: options[:runner_conf][:input] # The --input name=value CLI option
132
142
  )
133
143
 
134
144
  @runner_context =
@@ -84,6 +84,7 @@ require "inspec/resources/passwd"
84
84
  require "inspec/resources/pip"
85
85
  require "inspec/resources/platform"
86
86
  require "inspec/resources/port"
87
+ require "inspec/resources/postfix_conf"
87
88
  require "inspec/resources/postgres"
88
89
  require "inspec/resources/postgres_conf"
89
90
  require "inspec/resources/postgres_hba_conf"
@@ -0,0 +1,31 @@
1
+ require "inspec/resources/ini"
2
+ require "inspec/utils/simpleconfig"
3
+
4
+ module Inspec::Resources
5
+ class PostfixConf < IniConfig
6
+ name "postfix_conf"
7
+ supports platform: "linux"
8
+ desc "Use the postfix_conf Inspec audit resource to test the configuration of the Postfix Mail Transfer Agent"
9
+
10
+ # Allow user to specify a custom configuration path, use default Postfix configuration path if no custom path is provided
11
+ def initialize(*opts)
12
+ @params = {}
13
+ if opts.length == 1
14
+ @raw_content = load_raw_content(opts)
15
+ else
16
+ @raw_content = load_raw_content("/etc/postfix/main.cf")
17
+ end
18
+ @params = parse(@raw_content)
19
+ end
20
+
21
+ def parse(content)
22
+ SimpleConfig.new(content).params
23
+ end
24
+
25
+ private
26
+
27
+ def resource_base_name
28
+ "POSTFIX_CONF"
29
+ end
30
+ end
31
+ end
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "4.12.0".freeze
2
+ VERSION = "4.16.0".freeze
3
3
  end
@@ -6,6 +6,14 @@ require "inspec/dist"
6
6
  module InspecPlugins
7
7
  module PluginManager
8
8
  class CliCommand < Inspec.plugin(2, :cli_command)
9
+ INSTALL_TYPE_LABELS = {
10
+ bundle: "core", # Calling this core, too - not much of a distinction
11
+ core: "core",
12
+ path: "path",
13
+ user_gem: "gem (user)",
14
+ system_gem: "gem (system)",
15
+ }.freeze
16
+
9
17
  include Inspec::Dist
10
18
 
11
19
  subcommand_desc "plugin SUBCOMMAND", "Manage #{PRODUCT_NAME} and Train plugins"
@@ -15,22 +23,36 @@ module InspecPlugins
15
23
  #==================================================================#
16
24
 
17
25
  desc "list [options]", "Lists user-installed #{PRODUCT_NAME} plugins."
18
- option :all, desc: "Include plugins shipped with #{PRODUCT_NAME} as well.", type: :boolean, aliases: [:a]
26
+ option :all, desc: "List all types of plugins (default)", type: :boolean, default: true, aliases: [:a]
27
+ option :user, desc: "List user plugins, from ~/.inspec/gems", banner: "", type: :boolean, default: false, aliases: [:u]
28
+ option :system, desc: "List system plugins, those InSpec depends on", banner: "", type: :boolean, default: false, aliases: [:s]
29
+ option :core, desc: "List core plugins, those InSpec ships with", banner: "", type: :boolean, default: false, aliases: [:c]
30
+
19
31
  def list
20
32
  plugin_statuses = Inspec::Plugin::V2::Registry.instance.plugin_statuses
21
- plugin_statuses.reject! { |s| %i{core bundle}.include?(s.installation_type) } unless options[:all]
33
+ options[:all] = false if options[:core] || options[:user] || options[:system]
34
+ plugin_statuses.select! do |status|
35
+ type = status.installation_type
36
+ options[:all] ||
37
+ (options[:core] && %i{core bundle}.include?(type)) ||
38
+ (options[:user] && %i{user_gem path}.include?(type)) ||
39
+ (options[:system] && :system_gem == type)
40
+ end
22
41
 
23
- puts
24
- ui.bold(format(" %-30s%-10s%-8s%-6s", "Plugin Name", "Version", "Via", "ApiVer"))
25
- ui.line
26
- plugin_statuses.sort_by(&:name).each do |status|
27
- ui.plain(format(" %-30s%-10s%-8s%-6s", status.name,
28
- make_pretty_version(status),
29
- status.installation_type,
30
- status.api_generation.to_s))
42
+ unless plugin_statuses.empty?
43
+ ui.table do |t|
44
+ t.header = ["Plugin Name", "Version", "Via", "ApiVer"]
45
+ plugin_statuses.sort_by { |s| s.name.to_s }.each do |status|
46
+ t << [
47
+ status.name,
48
+ make_pretty_version(status),
49
+ make_pretty_install_type(status),
50
+ status.api_generation,
51
+ ]
52
+ end
53
+ end
31
54
  end
32
- ui.line
33
- ui.plain(" #{plugin_statuses.count} plugin(s) total")
55
+ ui.plain_line(" #{plugin_statuses.count} plugin(s) total")
34
56
  puts
35
57
  end
36
58
 
@@ -60,15 +82,15 @@ module InspecPlugins
60
82
  end
61
83
 
62
84
  puts
63
- ui.bold(format(" %-30s%-50s", "Plugin Name", "Versions Available"))
85
+ ui.bold(format(" %-30s%-50s\n", "Plugin Name", "Versions Available"))
64
86
  ui.line
65
87
  search_results.keys.sort.each do |plugin_name|
66
88
  versions = options[:all] ? search_results[plugin_name] : [search_results[plugin_name].first]
67
89
  versions = "(" + versions.join(", ") + ")"
68
- ui.plain(format(" %-30s%-50s", plugin_name, versions))
90
+ ui.plain_line(format(" %-30s%-50s", plugin_name, versions))
69
91
  end
70
92
  ui.line
71
- ui.plain(" #{search_results.count} plugin(s) found")
93
+ ui.plain_line(" #{search_results.count} plugin(s) found")
72
94
  puts
73
95
 
74
96
  ui.exit Inspec::UI::EXIT_PLUGIN_ERROR if search_results.empty?
@@ -118,14 +140,14 @@ module InspecPlugins
118
140
  begin
119
141
  installer.update(plugin_name)
120
142
  rescue Inspec::Plugin::V2::UpdateError => ex
121
- ui.plain("#{ui.red('Update error:')} #{ex.message} - update failed")
143
+ ui.plain_line("#{ui.red("Update error:", print: false)} #{ex.message} - update failed")
122
144
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
123
145
  end
124
146
  post_update_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s }
125
147
  new_version = (post_update_versions - pre_update_versions).first
126
148
 
127
149
  ui.bold(plugin_name + " plugin, version #{old_version} -> " \
128
- "#{new_version}, updated from rubygems.org")
150
+ "#{new_version}, updated from rubygems.org\n")
129
151
  end
130
152
 
131
153
  #--------------------------
@@ -144,7 +166,7 @@ module InspecPlugins
144
166
  def uninstall(plugin_name)
145
167
  status = Inspec::Plugin::V2::Registry.instance[plugin_name.to_sym]
146
168
  unless status
147
- ui.plain("#{ui.red('No such plugin installed:')} #{plugin_name} is not " \
169
+ ui.plain_line("#{ui.red("No such plugin installed:", print: false)} #{plugin_name} is not " \
148
170
  "installed - uninstall failed")
149
171
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
150
172
  end
@@ -157,11 +179,12 @@ module InspecPlugins
157
179
 
158
180
  if status.installation_type == :path
159
181
  ui.bold(plugin_name + " path-based plugin install has been " \
160
- "uninstalled")
182
+ "uninstalled\n")
161
183
  else
162
184
  ui.bold(plugin_name + " plugin, version #{old_version}, has " \
163
- "been uninstalled")
185
+ "been uninstalled\n")
164
186
  end
187
+
165
188
  ui.exit Inspec::UI::EXIT_NORMAL
166
189
  end
167
190
 
@@ -174,7 +197,7 @@ module InspecPlugins
174
197
 
175
198
  def install_from_gemfile(gem_file)
176
199
  unless File.exist? gem_file
177
- ui.red("No such plugin gem file #{gem_file} - installation failed.")
200
+ ui.red("No such plugin gem file #{gem_file} - installation failed.\n")
178
201
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
179
202
  end
180
203
 
@@ -186,13 +209,13 @@ module InspecPlugins
186
209
  installer.install(plugin_name, gem_file: gem_file)
187
210
 
188
211
  ui.bold("#{plugin_name} plugin, version #{version}, installed from " \
189
- "local .gem file")
212
+ "local .gem file\n")
190
213
  ui.exit Inspec::UI::EXIT_NORMAL
191
214
  end
192
215
 
193
216
  def install_from_path(path)
194
217
  unless File.exist? path
195
- ui.red("No such source code path #{path} - installation failed.")
218
+ ui.red("No such source code path #{path} - installation failed.\n")
196
219
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
197
220
  end
198
221
 
@@ -209,7 +232,7 @@ module InspecPlugins
209
232
  if registry.known_plugin?(plugin_name.to_sym)
210
233
  ui.red("Plugin already installed - #{plugin_name} - Use '#{EXEC_NAME} " \
211
234
  "plugin list' to see previously installed plugin - " \
212
- "installation failed.")
235
+ "installation failed.\n")
213
236
  ui.exit Inspec::UI::EXIT_PLUGIN_ERROR
214
237
  end
215
238
 
@@ -223,7 +246,7 @@ module InspecPlugins
223
246
  installer.install(plugin_name, path: entry_point)
224
247
 
225
248
  ui.bold("#{plugin_name} plugin installed via source path reference, " \
226
- "resolved to entry point #{entry_point}")
249
+ "resolved to entry point #{entry_point}\n")
227
250
  ui.exit Inspec::UI::EXIT_NORMAL
228
251
  end
229
252
 
@@ -288,7 +311,7 @@ module InspecPlugins
288
311
  # Give up.
289
312
  ui.red("Unrecognizable plugin structure - #{parts[2]} - When " \
290
313
  "installing from a path, please provide the path of the " \
291
- "entry point file - installation failed.")
314
+ "entry point file - installation failed.\n")
292
315
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
293
316
  end
294
317
 
@@ -299,8 +322,8 @@ module InspecPlugins
299
322
  rescue LoadError => ex
300
323
  ui.red("Plugin contains errors - #{plugin_name} - Encountered " \
301
324
  "errors while trying to test load the plugin entry point, " \
302
- "resolved to #{entry_point} - installation failed")
303
- ui.plain ex.message
325
+ "resolved to #{entry_point} - installation failed\n")
326
+ ui.plain_line ex.message
304
327
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
305
328
  end
306
329
 
@@ -313,7 +336,7 @@ module InspecPlugins
313
336
  ui.red("Does not appear to be a plugin - #{plugin_name} - After " \
314
337
  "probe-loading the supposed plugin, it did not register " \
315
338
  "itself to Train. Ensure something inherits from " \
316
- "'Train.plugin(1)' - installation failed.")
339
+ "'Train.plugin(1)' - installation failed.\n")
317
340
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
318
341
  end
319
342
  else
@@ -321,7 +344,7 @@ module InspecPlugins
321
344
  ui.red("Does not appear to be a plugin - #{plugin_name} - After " \
322
345
  "probe-loading the supposed plugin, it did not register " \
323
346
  "itself to InSpec. Ensure something inherits from " \
324
- "'Inspec.plugin(2)' - installation failed.")
347
+ "'Inspec.plugin(2)' - installation failed.\n")
325
348
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
326
349
  end
327
350
  end
@@ -343,7 +366,7 @@ module InspecPlugins
343
366
  new_version = (post_installed_versions - pre_installed_versions).first
344
367
 
345
368
  ui.bold("#{plugin_name} plugin, version #{new_version}, installed " \
346
- "from rubygems.org")
369
+ "from rubygems.org\n")
347
370
  ui.exit Inspec::UI::EXIT_NORMAL
348
371
  end
349
372
 
@@ -367,16 +390,16 @@ module InspecPlugins
367
390
  what_we_would_install_is_already_installed = pre_installed_versions.include?(requested_version)
368
391
  if what_we_would_install_is_already_installed && they_explicitly_asked_for_a_version
369
392
  ui.red("Plugin already installed at requested version - plugin " \
370
- "#{plugin_name} #{requested_version} - refusing to install.")
393
+ "#{plugin_name} #{requested_version} - refusing to install.\n")
371
394
  elsif what_we_would_install_is_already_installed && !they_explicitly_asked_for_a_version
372
395
  ui.red("Plugin already installed at latest version - plugin " \
373
- "#{plugin_name} #{requested_version} - refusing to install.")
396
+ "#{plugin_name} #{requested_version} - refusing to install.\n")
374
397
  else
375
398
  # There are existing versions installed, but none of them are what was requested
376
399
  ui.red("Update required - plugin #{plugin_name}, requested " \
377
400
  "#{requested_version}, have " \
378
- "#{pre_installed_versions.join(', ')}; use `inspec " \
379
- "plugin update` - refusing to install.")
401
+ "#{pre_installed_versions.join(", ")}; use `inspec " \
402
+ "plugin update` - refusing to install.\n")
380
403
  end
381
404
 
382
405
  ui.exit Inspec::UI::EXIT_PLUGIN_ERROR
@@ -387,11 +410,11 @@ module InspecPlugins
387
410
  installer.install(plugin_name, version: options[:version])
388
411
  rescue Inspec::Plugin::V2::PluginExcludedError => ex
389
412
  ui.red("Plugin on Exclusion List - #{plugin_name} is listed as an " \
390
- "incompatible gem - refusing to install.")
391
- ui.plain("Rationale: #{ex.details.rationale}")
392
- ui.plain("Exclusion list location: " +
413
+ "incompatible gem - refusing to install.\n")
414
+ ui.plain_line("Rationale: #{ex.details.rationale}")
415
+ ui.plain_line("Exclusion list location: " +
393
416
  File.join(Inspec.src_root, "etc", "plugin_filters.json"))
394
- ui.plain("If you disagree with this determination, please accept " \
417
+ ui.plain_line("If you disagree with this determination, please accept " \
395
418
  "our apologies for the misunderstanding, and open an issue " \
396
419
  "at https://github.com/inspec/inspec/issues/new")
397
420
  ui.exit Inspec::UI::EXIT_PLUGIN_ERROR
@@ -401,13 +424,13 @@ module InspecPlugins
401
424
  results = installer.search(plugin_name, exact: true)
402
425
  if results.empty?
403
426
  ui.red("No such plugin gem #{plugin_name} could be found on " \
404
- "rubygems.org - installation failed.")
427
+ "rubygems.org - installation failed.\n")
405
428
  elsif options[:version] && !results[plugin_name].include?(options[:version])
406
429
  ui.red("No such version - #{plugin_name} exists, but no such " \
407
430
  "version #{options[:version]} found on rubygems.org - " \
408
- "installation failed.")
431
+ "installation failed.\n")
409
432
  else
410
- ui.red("Unknown error occured - installation failed.")
433
+ ui.red("Unknown error occured - installation failed.\n")
411
434
  end
412
435
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
413
436
  end
@@ -420,10 +443,10 @@ module InspecPlugins
420
443
  # Check for path install
421
444
  status = Inspec::Plugin::V2::Registry.instance[plugin_name.to_sym]
422
445
  if !status
423
- ui.plain("#{ui.red('No such plugin installed:')} #{plugin_name} - update failed")
446
+ ui.plain_line("#{ui.red("No such plugin installed:", print: false)} #{plugin_name} - update failed")
424
447
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
425
448
  elsif status.installation_type == :path
426
- ui.plain("#{ui.red('Cannot update path-based install:')} " \
449
+ ui.plain_line("#{ui.red("Cannot update path-based install:", print: false)} " \
427
450
  "#{plugin_name} is installed via path reference; " \
428
451
  "use `inspec plugin uninstall` to remove - refusing to" \
429
452
  "update")
@@ -436,7 +459,7 @@ module InspecPlugins
436
459
  latest_version = latest_version[plugin_name]&.last
437
460
 
438
461
  if pre_update_versions.include?(latest_version)
439
- ui.plain("#{ui.red('Already installed at latest version:')} " \
462
+ ui.plain_line("#{ui.red("Already installed at latest version:", print: false)} " \
440
463
  "#{plugin_name} is at #{latest_version}, which the " \
441
464
  "latest - refusing to update")
442
465
  ui.exit Inspec::UI::EXIT_PLUGIN_ERROR
@@ -458,7 +481,7 @@ module InspecPlugins
458
481
  unless plugin_name =~ /^(inspec|train)-/
459
482
  ui.red("Invalid plugin name - #{plugin_name} - All inspec " \
460
483
  "plugins must begin with either 'inspec-' or 'train-' " \
461
- "- #{action} failed.")
484
+ "- #{action} failed.\n")
462
485
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
463
486
  end
464
487
  end
@@ -467,17 +490,29 @@ module InspecPlugins
467
490
  case status.installation_type
468
491
  when :core, :bundle
469
492
  Inspec::VERSION
470
- when :gem
471
- # TODO: this is naive, and assumes the latest version is the one that will be used. Logged on #3317
472
- # In fact, the logic to determine "what version would be used" belongs in the Loader.
473
- Inspec::Plugin::V2::Loader.list_installed_plugin_gems
474
- .select { |spec| spec.name == status.name.to_s }
475
- .sort_by(&:version)
476
- .last.version
493
+ when :user_gem, :system_gem
494
+ if status.version.nil?
495
+ "(unknown)"
496
+ elsif status.version =~ /^\d+\.\d+\.\d+$/
497
+ status.version
498
+ else
499
+ # Assume it is a version constraint string and try to resolve
500
+ # TODO: this is naive, and assumes the latest version is the one that will be used. Logged on #3317
501
+ # In fact, the logic to determine "what version would be used" belongs in the Loader.
502
+ plugin_name = status.name.to_s
503
+ Inspec::Plugin::V2::Loader.list_installed_plugin_gems
504
+ .select { |spec| spec.name == plugin_name }
505
+ .sort_by(&:version)
506
+ .last.version
507
+ end
477
508
  when :path
478
509
  "src"
479
510
  end
480
511
  end
512
+
513
+ def make_pretty_install_type(status)
514
+ INSTALL_TYPE_LABELS[status.installation_type]
515
+ end
481
516
  end
482
517
  end
483
518
  end
@@ -40,25 +40,27 @@ module SourceReaders
40
40
  raise "Unable to parse #{metadata_source}: #{e.class} -- #{e.message}"
41
41
  end
42
42
 
43
+ def find_all(regexp)
44
+ @target.files.grep(regexp)
45
+ end
46
+
47
+ def load_all(regexp)
48
+ find_all(regexp)
49
+ .map { |path| file = @target.read(path); [path, file] if file }
50
+ .compact
51
+ .to_h
52
+ end
53
+
43
54
  def load_tests
44
- tests = @target.files.find_all do |path|
45
- path.start_with?("controls") && path.end_with?(".rb")
46
- end
47
- Hash[tests.map { |x| [x, @target.read(x)] }.delete_if { |_file, contents| contents.nil? }]
55
+ load_all(%r{^controls/.*\.rb$})
48
56
  end
49
57
 
50
58
  def load_libs
51
- tests = @target.files.find_all do |path|
52
- path.start_with?("libraries") && path.end_with?(".rb")
53
- end
54
- Hash[tests.map { |x| [x, @target.read(x)] }.delete_if { |_file, contents| contents.nil? }]
59
+ load_all(%r{^libraries/.*\.rb$})
55
60
  end
56
61
 
57
62
  def load_data_files
58
- files = @target.files.find_all do |path|
59
- path.start_with?("files" + File::SEPARATOR)
60
- end
61
- Hash[files.map { |x| [x, @target.read(x)] }.delete_if { |_file, contents| contents.nil? }]
63
+ load_all(%r{^files/})
62
64
  end
63
65
  end
64
66
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.12.0
4
+ version: 4.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Richter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-15 00:00:00.000000000 Z
11
+ date: 2019-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: train-core
@@ -536,6 +536,7 @@ files:
536
536
  - lib/inspec/resources/pip.rb
537
537
  - lib/inspec/resources/platform.rb
538
538
  - lib/inspec/resources/port.rb
539
+ - lib/inspec/resources/postfix_conf.rb
539
540
  - lib/inspec/resources/postgres.rb
540
541
  - lib/inspec/resources/postgres_conf.rb
541
542
  - lib/inspec/resources/postgres_hba_conf.rb