inspec-core 5.22.50 → 6.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/Chef-EULA +9 -0
  3. data/Gemfile +14 -4
  4. data/etc/features.sig +6 -0
  5. data/etc/features.yaml +97 -0
  6. data/inspec-core.gemspec +17 -7
  7. data/lib/inspec/backend.rb +2 -0
  8. data/lib/inspec/base_cli.rb +80 -4
  9. data/lib/inspec/cached_fetcher.rb +24 -3
  10. data/lib/inspec/cli.rb +293 -236
  11. data/lib/inspec/config.rb +24 -2
  12. data/lib/inspec/dependencies/cache.rb +33 -0
  13. data/lib/inspec/enhanced_outcomes.rb +1 -0
  14. data/lib/inspec/errors.rb +5 -0
  15. data/lib/inspec/exceptions.rb +2 -0
  16. data/lib/inspec/feature/config.rb +75 -0
  17. data/lib/inspec/feature/runner.rb +29 -0
  18. data/lib/inspec/feature.rb +42 -0
  19. data/lib/inspec/fetcher/git.rb +5 -0
  20. data/lib/inspec/fetcher/url.rb +24 -4
  21. data/lib/inspec/globals.rb +6 -0
  22. data/lib/inspec/iaf_file.rb +3 -2
  23. data/lib/inspec/input_registry.rb +5 -1
  24. data/lib/inspec/plugin/v1/plugin_types/fetcher.rb +7 -0
  25. data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +30 -2
  26. data/lib/inspec/profile.rb +44 -1
  27. data/lib/inspec/reporters.rb +67 -54
  28. data/lib/inspec/resources/nftables.rb +14 -1
  29. data/lib/inspec/resources/oracledb_session.rb +12 -3
  30. data/lib/inspec/resources/ssh_config.rb +100 -9
  31. data/lib/inspec/resources/ssh_key.rb +124 -0
  32. data/lib/inspec/resources/sshd_active_config.rb +2 -0
  33. data/lib/inspec/resources/sybase_session.rb +11 -2
  34. data/lib/inspec/resources.rb +1 -0
  35. data/lib/inspec/rule.rb +6 -6
  36. data/lib/inspec/run_data.rb +7 -5
  37. data/lib/inspec/runner.rb +43 -6
  38. data/lib/inspec/runner_rspec.rb +12 -9
  39. data/lib/inspec/secrets/yaml.rb +9 -3
  40. data/lib/inspec/shell.rb +10 -0
  41. data/lib/inspec/ui.rb +4 -0
  42. data/lib/inspec/utils/licensing_config.rb +9 -0
  43. data/lib/inspec/utils/telemetry/base.rb +149 -0
  44. data/lib/inspec/utils/telemetry/http.rb +40 -0
  45. data/lib/inspec/utils/telemetry/null.rb +11 -0
  46. data/lib/inspec/utils/telemetry/run_context_probe.rb +13 -1
  47. data/lib/inspec/utils/telemetry.rb +74 -3
  48. data/lib/inspec/version.rb +1 -1
  49. data/lib/inspec/waiver_file_reader.rb +68 -27
  50. data/lib/inspec.rb +2 -2
  51. data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +189 -168
  52. data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +10 -3
  53. data/lib/plugins/inspec-init/lib/inspec-init/cli.rb +1 -0
  54. data/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb +23 -21
  55. data/lib/plugins/inspec-init/lib/inspec-init/cli_profile.rb +15 -13
  56. data/lib/plugins/inspec-init/lib/inspec-init/cli_resource.rb +15 -13
  57. data/lib/plugins/inspec-license/README.md +16 -0
  58. data/lib/plugins/inspec-license/inspec-license.gemspec +6 -0
  59. data/lib/plugins/inspec-license/lib/inspec-license/cli.rb +26 -0
  60. data/lib/plugins/inspec-license/lib/inspec-license.rb +14 -0
  61. data/lib/plugins/inspec-parallel/README.md +27 -0
  62. data/lib/plugins/inspec-parallel/inspec-parallel.gemspec +6 -0
  63. data/lib/plugins/inspec-parallel/lib/inspec-parallel/child_status_reporter.rb +61 -0
  64. data/lib/plugins/inspec-parallel/lib/inspec-parallel/cli.rb +39 -0
  65. data/lib/plugins/inspec-parallel/lib/inspec-parallel/command.rb +219 -0
  66. data/lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb +270 -0
  67. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/base.rb +24 -0
  68. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/silent.rb +7 -0
  69. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/status.rb +125 -0
  70. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/text.rb +23 -0
  71. data/lib/plugins/inspec-parallel/lib/inspec-parallel/validator.rb +170 -0
  72. data/lib/plugins/inspec-parallel/lib/inspec-parallel.rb +18 -0
  73. data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +20 -8
  74. data/lib/plugins/inspec-sign/lib/inspec-sign/cli.rb +11 -4
  75. data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +6 -13
  76. metadata +61 -19
  77. data/lib/inspec/utils/telemetry/collector.rb +0 -81
  78. data/lib/inspec/utils/telemetry/data_series.rb +0 -44
  79. data/lib/inspec/utils/telemetry/global_methods.rb +0 -22
@@ -135,7 +135,20 @@ module Inspec::Resources
135
135
  cmd = inspec.command(nftables_cmd)
136
136
  return [] if cmd.exit_status.to_i != 0
137
137
 
138
- @nftables_cache[idx] = cmd.stdout.gsub("\t", "").split("\n").reject { |line| line =~ /^(table|set|type|size|flags|typeof|auto-merge)/ || line =~ /^}$/ }.map { |line| line.sub("elements = {", "").sub("}", "").split(",") }.flatten.map(&:strip)
138
+ # https://github.com/inspec/inspec/security/code-scanning/10
139
+ # Update @nftables_cache with sanitized command output
140
+ @nftables_cache[idx] = cmd.stdout.gsub("\t", "").split("\n")
141
+ .reject { |line| line =~ /^(table|set|type|size|flags|typeof|auto-merge)/ || line =~ /^}$/ } # Reject lines that match certain patterns
142
+ .map { |line| line.gsub("elements = {", "").gsub("}", "").split(",") } # Use gsub to replace all occurrences of specified strings
143
+ .flatten # Flatten the array of arrays into a single array
144
+ .map(&:strip) # Remove leading and trailing whitespace from each element
145
+ .map { |element| sanitize_input(element) } # Sanitize each element to prevent injection attacks
146
+ end
147
+
148
+ # Method to sanitize input
149
+ def sanitize_input(input)
150
+ # Replace potentially dangerous characters with their escaped counterparts
151
+ input.gsub(/([\\'";])/, '\\\\\1')
139
152
  end
140
153
 
141
154
  def retrieve_chain_rules
@@ -96,8 +96,7 @@ module Inspec::Resources
96
96
  if @db_role.nil? || @su_user.nil?
97
97
  verified_query = verify_query(query)
98
98
  else
99
- escaped_query = query.gsub(/\\\\/, "\\").gsub(/"/, '\\"')
100
- escaped_query = escaped_query.gsub("$", '\\$') unless escaped_query.include? "\\$"
99
+ escaped_query = escape_query(query)
101
100
  verified_query = verify_query(escaped_query)
102
101
  end
103
102
 
@@ -134,11 +133,21 @@ module Inspec::Resources
134
133
  query
135
134
  end
136
135
 
136
+ def escape_query(query)
137
+ # https://github.com/inspec/inspec/security/code-scanning/7
138
+ # https://github.com/inspec/inspec/security/code-scanning/8
139
+ escaped_query = query.gsub(/["\\]/) { |match| match == '"' ? '\\"' : "\\\\" } # Escape backslashes and double quotes
140
+ escaped_query.gsub!("$", '\\$') unless escaped_query.include? "\\$" # Escape dollar signs, but only if not already escaped
141
+ escaped_query
142
+ end
143
+
137
144
  def parse_csv_result(stdout)
138
145
  output = stdout.split("oracle_query_string")[-1]
139
146
  # comma_query_sub replaces the csv delimiter "," in the output.
140
147
  # Handles CSV parsing of data like this (DROP,3) etc
141
- output = output.sub(/\r/, "").strip.gsub(",", "comma_query_sub")
148
+ # Replace all occurrences of the target pattern using gsub instead of sub
149
+ # Issue detected: https://github.com/inspec/inspec/security/code-scanning/9
150
+ output = output.gsub(/\r/, "").strip.gsub(",", "comma_query_sub")
142
151
  converter = ->(header) { header.downcase }
143
152
  CSV.parse(output, headers: true, header_converters: converter).map do |row|
144
153
  next if row.entries.flatten.empty?
@@ -38,16 +38,13 @@ module Inspec::Resources
38
38
 
39
39
  def convert_hash(hash)
40
40
  new_hash = {}
41
- hash.each do |k, v|
42
- new_hash[k.downcase] ||= v
43
- end
41
+ hash.each { |k, v| new_hash[k.downcase] ||= v }
44
42
  new_hash
45
43
  end
46
44
 
47
45
  def method_missing(name)
48
46
  param = read_params[name.to_s.downcase]
49
47
  return nil if param.nil?
50
- # extract first value if we have only one value in array
51
48
  return param[0] if param.length == 1
52
49
 
53
50
  param
@@ -73,11 +70,12 @@ module Inspec::Resources
73
70
  return @params if defined?(@params)
74
71
  return @params = {} if read_content.nil?
75
72
 
76
- conf = SimpleConfig.new(
77
- read_content,
78
- assignment_regex: /^\s*(\S+?)\s+(.*?)\s*$/,
79
- multiple_values: true
80
- )
73
+ conf =
74
+ SimpleConfig.new(
75
+ read_content,
76
+ assignment_regex: /^\s*(\S+?)\s+(.*?)\s*$/,
77
+ multiple_values: true
78
+ )
81
79
  @params = convert_hash(conf.params)
82
80
  end
83
81
 
@@ -121,4 +119,97 @@ module Inspec::Resources
121
119
  "/etc/ssh/#{type}"
122
120
  end
123
121
  end
122
+
123
+ class SshdActiveConfig < SshdConfig
124
+ name "sshd_active_config"
125
+ supports platform: "unix"
126
+ supports platform: "windows"
127
+ desc "Use the sshd_active_config InSpec audit resource to test configuration data for the Open SSH daemon located at /etc/ssh/sshd_config on Linux and UNIX platforms. sshd---the Open SSH daemon---listens on dedicated ports, starts a daemon for each incoming connection, and then handles encryption, authentication, key exchanges, command execution, and data exchanges."
128
+ example <<~EXAMPLE
129
+ describe sshd_active_config do
130
+ its('Protocol') { should eq '2' }
131
+ end
132
+ EXAMPLE
133
+
134
+ attr_reader :active_path
135
+
136
+ def initialize
137
+ @active_path = dynamic_sshd_config_path
138
+ super(@active_path)
139
+ end
140
+
141
+ def to_s
142
+ "SSHD Active Configuration (active path: #{@conf_path})"
143
+ end
144
+
145
+ private
146
+
147
+ def ssh_config_file(type)
148
+ if inspec.os.windows?
149
+ programdata = inspec.os_env("programdata").content
150
+ return "#{programdata}\\ssh\\#{type}"
151
+ end
152
+
153
+ "/etc/ssh/#{type}"
154
+ end
155
+
156
+ def dynamic_sshd_config_path
157
+ if inspec.os.windows?
158
+ script = <<-EOH
159
+ $sshdPath = (Get-Command sshd.exe).Source
160
+ if ($sshdPath -ne $null) {
161
+ Write-Output $sshdPath
162
+ } else {
163
+ Write-Error "sshd.exe not found"
164
+ }
165
+ EOH
166
+ sshd_path_result = inspec.powershell(script).stdout.strip
167
+ sshd_path = "\"#{sshd_path_result}\""
168
+ if !sshd_path_result.empty? && sshd_path_result != "sshd.exe not found"
169
+ command_output = inspec.command("sudo #{sshd_path} -dd 2>&1").stdout
170
+ dynamic_path =
171
+ command_output
172
+ .lines
173
+ .find { |line| line.include?("filename") }
174
+ &.split("filename")
175
+ &.last
176
+ &.strip
177
+ env_var_name = dynamic_path.match(/__(.*?)__/)[1]
178
+ if env_var_name?
179
+ dynamic_path =
180
+ dynamic_path.gsub(
181
+ /__#{env_var_name}__/,
182
+ inspec.os_env(env_var_name).content
183
+ )
184
+ end
185
+ else
186
+ Inspec::Log.error("sshd.exe not found using PowerShell script block.")
187
+ return nil
188
+ end
189
+ elsif inspec.os.unix?
190
+ sshd_path = "/usr/sbin/sshd"
191
+ command_output = inspec.command("sudo #{sshd_path} -dd 2>&1").stdout
192
+ dynamic_path =
193
+ command_output
194
+ .lines
195
+ .find { |line| line.include?("filename") }
196
+ &.split("filename")
197
+ &.last
198
+ &.strip
199
+ else
200
+ Inspec::Log.error(
201
+ "Unable to determine sshd configuration path on Windows using -T flag."
202
+ )
203
+ return nil
204
+ end
205
+
206
+ if dynamic_path.nil? || dynamic_path.empty?
207
+ Inspec::Log.warn(
208
+ "No active SSHD configuration found. Using default configuration."
209
+ )
210
+ return ssh_config_file("sshd_config")
211
+ end
212
+ dynamic_path
213
+ end
214
+ end
124
215
  end
@@ -0,0 +1,124 @@
1
+ require "inspec/utils/file_reader"
2
+ require "net/ssh" unless defined?(Net::SSH)
3
+
4
+ # Change module if required
5
+ module Inspec::Resources
6
+ class SshKey < FileResource
7
+ # Every resource requires an internal name.
8
+ name "ssh_key"
9
+
10
+ # Restrict to only run on the below platforms (if none were given,
11
+ # all OS's and cloud API's supported)
12
+ supports platform: "unix"
13
+ supports platform: "windows"
14
+
15
+ desc "public/private SSH key pair test"
16
+
17
+ example <<~EXAMPLE
18
+ describe ssh_key('path: ~/.ssh/id_rsa') do
19
+ its('key_length') { should eq 4096 }
20
+ its('type') { should cmp /rsa/ }
21
+ it { should be_private }
22
+ end
23
+ EXAMPLE
24
+
25
+ include FileReader
26
+
27
+ def initialize(keypath, passphrase = nil)
28
+ skip_resource "The `ssh_key` resource is not yet available on your OS." unless inspec.os.unix? || inspec.os.windows?
29
+ @key_path = set_ssh_key_path(keypath)
30
+ @passphrase = passphrase
31
+ @key = read_ssh_key
32
+ super(@key_path)
33
+ end
34
+
35
+ def public?
36
+ return if @key.nil?
37
+
38
+ @key[:public]
39
+ end
40
+
41
+ def private?
42
+ return if @key.nil?
43
+
44
+ @key[:private]
45
+ end
46
+
47
+ def key_length
48
+ return if @key.nil?
49
+
50
+ @key[:key_length]
51
+ end
52
+
53
+ def type
54
+ return if @key.nil?
55
+
56
+ @key[:type]
57
+ end
58
+
59
+ # Define a resource ID. This is used in reporting engines to uniquely identify the individual resource.
60
+ # This might be a file path, or a process ID, or a cloud instance ID. Only meaningful to the implementation.
61
+ # Must be a string. Defaults to the empty string if not implemented.
62
+ def resource_id
63
+ @key_path || "SSH key"
64
+ end
65
+
66
+ def to_s
67
+ "ssh_key #{@key_path}"
68
+ end
69
+
70
+ private
71
+
72
+ def set_ssh_key_path(keypath)
73
+ if File.exist?(keypath)
74
+ @key_path = keypath
75
+ elsif File.exist?(File.join("#{Dir.home}/.ssh/", keypath))
76
+ @key_path = File.join("#{Dir.home}/.ssh/", keypath)
77
+ else
78
+ raise Inspec::Exceptions::ResourceSkipped, "Can't find file: #{keypath}"
79
+ end
80
+ end
81
+
82
+ def read_ssh_key
83
+ key_data = {}
84
+ key = nil
85
+ filecontent = read_file_content((@key_path), @passphrase)
86
+ raise Inspec::Exceptions::ResourceSkipped, "File is empty: #{@key_path}" if filecontent.split("\n").empty?
87
+
88
+ if filecontent.split("\n")[0].include?("PRIVATE")
89
+ # Net::SSH::KeyFactory does not have support to load private key for DSA
90
+ key = Net::SSH::KeyFactory.load_private_key(@key_path, @passphrase, false)
91
+ unless key.nil?
92
+ key_data[:private] = true
93
+ key_data[:public] = false
94
+ # The data send for ssh type is not in same format so it's good to match on the string
95
+ key_data[:type] = key.ssh_type
96
+ key_data[:key_length] = key_lengh(key)
97
+ end
98
+ else
99
+ key = Net::SSH::KeyFactory.load_public_key(@key_path)
100
+ unless key.nil?
101
+ key_data[:private] = false
102
+ key_data[:public] = true
103
+ # The data send for ssh type is not in same format so it's good to match on the string
104
+ key_data[:type] = key.ssh_type
105
+ key_data[:key_length] = key_lengh(key)
106
+ end
107
+ end
108
+
109
+ key_data
110
+ rescue OpenSSL::PKey::PKeyError => e
111
+ raise Inspec::Exceptions::ResourceFailed, "#{e.message}"
112
+ end
113
+
114
+ def key_lengh(key)
115
+ if key.class.to_s == "OpenSSL::PKey::RSA"
116
+ key.public_key.n.num_bits
117
+ else
118
+ # Unable to get the key lenght data for other types of keys
119
+ # TODO: Need to check if there is any method that will get this info.
120
+ nil
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,2 @@
1
+ # This is just here to make the dynamic loader happy.
2
+ require "inspec/resources/ssh_config"
@@ -44,10 +44,19 @@ module Inspec::Resources
44
44
  # try to get a temp path
45
45
  sql_file_path = upload_sql_file(sql)
46
46
 
47
+ # TODO: Find if there is better way to get the current shell
48
+ current_shell = inspec.command("echo $SHELL")
49
+
50
+ res = current_shell.exit_status
51
+
47
52
  # isql reuires that we have a matching locale set, but does not support C.UTF-8. en_US.UTF-8 is the least evil.
48
- command = "LANG=en_US.UTF-8 SYBASE=#{sybase_home} #{bin} -s\"#{col_sep}\" -w80000 -S #{server} -U #{username} -D #{database} -P \"#{password}\" < #{sql_file_path}"
49
- isql_cmd = inspec.command(command)
53
+ if res == 0 && ( current_shell.stdout&.include?("/csh") || current_shell.stdout&.include?("/tcsh") )
54
+ command = "source #{sybase_home}/SYBASE.csh; setenv LANG en_US.UTF-8; #{bin} -s\"#{col_sep}\" -w80000 -S #{server} -U #{username} -D #{database} -P \"#{password}\" < #{sql_file_path}"
55
+ else
56
+ command = "LANG=en_US.UTF-8 SYBASE=#{sybase_home} #{bin} -s\"#{col_sep}\" -w80000 -S #{server} -U #{username} -D #{database} -P \"#{password}\" < #{sql_file_path}"
57
+ end
50
58
 
59
+ isql_cmd = inspec.command(command)
51
60
  # Check for isql errors
52
61
  res = isql_cmd.exit_status
53
62
  raise Inspec::Exceptions::ResourceFailed.new("isql exited with code #{res} and stderr '#{isql_cmd.stderr}', stdout '#{isql_cmd.stdout}'") unless res == 0
@@ -110,6 +110,7 @@ require "inspec/resources/selinux"
110
110
  require "inspec/resources/service"
111
111
  require "inspec/resources/shadow"
112
112
  require "inspec/resources/ssh_config"
113
+ require "inspec/resources/ssh_key"
113
114
  require "inspec/resources/ssl"
114
115
  require "inspec/resources/sys_info"
115
116
  require "inspec/resources/toml"
data/lib/inspec/rule.rb CHANGED
@@ -379,12 +379,7 @@ module Inspec
379
379
  control_id = @__rule_id # TODO: control ID slugging
380
380
 
381
381
  waiver_files = Inspec::Config.cached.final_options["waiver_file"] if Inspec::Config.cached.respond_to?(:final_options)
382
- unless waiver_files.nil? || waiver_files.empty?
383
- waiver_data_by_profile = Inspec::WaiverFileReader.fetch_waivers_by_profile(__profile_id, waiver_files)
384
- return unless waiver_data_by_profile && waiver_data_by_profile[control_id] && waiver_data_by_profile[control_id].is_a?(Hash)
385
-
386
- @__waiver_data = waiver_data_by_profile[control_id]
387
- else
382
+ if waiver_files.nil? || waiver_files.empty?
388
383
  # Support for input registry is provided for backward compatibilty with compliance phase of chef-client
389
384
  # Chef-client sends waiver information in inputs hash
390
385
  input_registry = Inspec::InputRegistry.instance
@@ -392,6 +387,11 @@ module Inspec
392
387
  return unless waiver_data_via_input && waiver_data_via_input.has_value? && waiver_data_via_input.value.is_a?(Hash)
393
388
 
394
389
  @__waiver_data = waiver_data_via_input.value
390
+ else
391
+ waiver_data_by_profile = Inspec::WaiverFileReader.fetch_waivers_by_profile(__profile_id, waiver_files)
392
+ return unless waiver_data_by_profile && waiver_data_by_profile[control_id] && waiver_data_by_profile[control_id].is_a?(Hash)
393
+
394
+ @__waiver_data = waiver_data_by_profile[control_id]
395
395
  end
396
396
 
397
397
  __waiver_data["skipped_due_to_waiver"] = false
@@ -27,11 +27,13 @@ module Inspec
27
27
  ) do
28
28
  include HashLikeStruct
29
29
  def initialize(raw_run_data)
30
- self.controls = raw_run_data[:controls].map { |c| Inspec::RunData::Control.new(c) }
31
- self.profiles = raw_run_data[:profiles].map { |p| Inspec::RunData::Profile.new(p) }
32
- self.statistics = Inspec::RunData::Statistics.new(raw_run_data[:statistics])
33
- self.platform = Inspec::RunData::Platform.new(raw_run_data[:platform])
34
- self.version = raw_run_data[:version]
30
+ @raw_run_data = raw_run_data
31
+
32
+ self.controls = @raw_run_data[:controls].map { |c| Inspec::RunData::Control.new(c) }
33
+ self.profiles = @raw_run_data[:profiles].map { |p| Inspec::RunData::Profile.new(p) }
34
+ self.statistics = Inspec::RunData::Statistics.new(@raw_run_data[:statistics])
35
+ self.platform = Inspec::RunData::Platform.new(@raw_run_data[:platform])
36
+ self.version = @raw_run_data[:version]
35
37
  end
36
38
  end
37
39
 
data/lib/inspec/runner.rb CHANGED
@@ -11,6 +11,8 @@ require "inspec/dependencies/cache"
11
11
  require "inspec/dist"
12
12
  require "inspec/reporters"
13
13
  require "inspec/runner_rspec"
14
+ require "chef-licensing"
15
+ require "inspec/utils/telemetry"
14
16
  # spec requirements
15
17
 
16
18
  module Inspec
@@ -60,11 +62,13 @@ module Inspec
60
62
  end
61
63
 
62
64
  if @conf[:waiver_file]
63
- @conf[:waiver_file].each do |file|
64
- unless File.file?(file)
65
- raise Inspec::Exceptions::WaiversFileDoesNotExist, "Waiver file #{file} does not exist."
65
+ Inspec.with_feature("inspec-waivers") {
66
+ @conf[:waiver_file].each do |file|
67
+ unless File.file?(file)
68
+ raise Inspec::Exceptions::WaiversFileDoesNotExist, "Waiver file #{file} does not exist."
69
+ end
66
70
  end
67
- end
71
+ }
68
72
  end
69
73
 
70
74
  # About reading inputs:
@@ -86,7 +90,12 @@ module Inspec
86
90
  end
87
91
 
88
92
  def configure_transport
89
- @backend = Inspec::Backend.create(@conf)
93
+ backend = Inspec::Backend.create(@conf)
94
+ set_backend(backend)
95
+ end
96
+
97
+ def set_backend(new_backend)
98
+ @backend = new_backend
90
99
  @test_collector.backend = @backend
91
100
  end
92
101
 
@@ -159,16 +168,43 @@ module Inspec
159
168
  end
160
169
 
161
170
  def run(with = nil)
171
+ ChefLicensing.check_software_entitlement! if Inspec::Dist::EXEC_NAME == "inspec"
172
+
173
+ # Validate if profiles are signed and verified
174
+ # Additional check is required to provide error message in case of inspec exec command (exec command can use multiple profiles as well)
175
+ # Only runs this block when preview flag CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING is set
176
+ Inspec.with_feature("inspec-mandatory-profile-signing") {
177
+ unless @conf.allow_unsigned_profiles?
178
+ verify_target_profiles_if_signed(@target_profiles)
179
+ end
180
+ }
181
+
162
182
  Inspec::Log.debug "Starting run with targets: #{@target_profiles.map(&:to_s)}"
183
+ Inspec::Telemetry.run_starting(runner: self, conf: @conf)
163
184
  load
164
185
  run_tests(with)
186
+ rescue ChefLicensing::SoftwareNotEntitled
187
+ Inspec::Log.error "License is not entitled to use InSpec."
188
+ Inspec::UI.new.exit(:license_not_entitled)
189
+ rescue ChefLicensing::Error => e
190
+ Inspec::Log.error e.message
191
+ Inspec::UI.new.exit(:usage_error)
192
+ end
193
+
194
+ def verify_target_profiles_if_signed(target_profiles)
195
+ unsigned_profiles = []
196
+ target_profiles.each do |profile|
197
+ unsigned_profiles << profile.name unless profile.verify_if_signed
198
+ end
199
+ raise Inspec::ProfileSignatureRequired, "Signature required for profile/s: #{unsigned_profiles.join(", ")}. Please provide a signed profile. Or set CHEF_ALLOW_UNSIGNED_PROFILES in the environment. Or use `--allow-unsigned-profiles` flag with InSpec CLI. " unless unsigned_profiles.empty?
165
200
  end
166
201
 
167
202
  def render_output(run_data)
168
203
  return if @conf["reporter"].nil?
169
204
 
170
205
  @conf["reporter"].each do |reporter|
171
- result = Inspec::Reporters.render(reporter, run_data, @conf["enhanced_outcomes"])
206
+ enhanced_outcome_flag = @conf["enhanced_outcomes"]
207
+ result = Inspec::Reporters.render(reporter, run_data, enhanced_outcome_flag)
172
208
  raise Inspec::ReporterError, "Error generating reporter '#{reporter[0]}'" if result == false
173
209
  end
174
210
  end
@@ -193,6 +229,7 @@ module Inspec
193
229
  @run_data = @test_collector.run(with)
194
230
  # dont output anything if we want a report
195
231
  render_output(@run_data) unless @conf["report"]
232
+ Inspec::Telemetry.run_ending(runner: self, run_data: @run_data, conf: @conf)
196
233
  @test_collector.exit_code
197
234
  end
198
235
 
@@ -177,16 +177,18 @@ module Inspec
177
177
  next unless streaming_reporters.include? streaming_reporter_name
178
178
 
179
179
  # Activate the plugin so the formatter ID gets registered with RSpec, presumably
180
- activator = reg.find_activator(plugin_type: :streaming_reporter, activator_name: streaming_reporter_name.to_sym)
181
- activator.activate!
182
180
 
183
- # We cannot pass in a nil output path. Rspec only accepts a valid string or a IO object.
184
- if file_target&.[]("file").nil?
185
- RSpec.configuration.add_formatter(activator.implementation_class)
186
- else
187
- RSpec.configuration.add_formatter(activator.implementation_class, file_target["file"])
188
- end
189
- @conf["reporter"].delete(streaming_reporter_name)
181
+ Inspec.with_feature("inspec-reporter-#{streaming_reporter_name}") {
182
+ activator = reg.find_activator(plugin_type: :streaming_reporter, activator_name: streaming_reporter_name.to_sym)
183
+ activator.activate!
184
+ # We cannot pass in a nil output path. Rspec only accepts a valid string or a IO object.
185
+ if file_target&.[]("file").nil?
186
+ RSpec.configuration.add_formatter(activator.implementation_class)
187
+ else
188
+ RSpec.configuration.add_formatter(activator.implementation_class, file_target["file"])
189
+ end
190
+ @conf["reporter"].delete(streaming_reporter_name)
191
+ }
190
192
  end
191
193
  end
192
194
 
@@ -196,6 +198,7 @@ module Inspec
196
198
  def configure_output
197
199
  RSpec.configuration.output_stream = $stdout
198
200
  @formatter = RSpec.configuration.add_formatter(Inspec::Formatters::Base)
201
+
199
202
  @formatter.enhanced_outcomes = @conf.final_options["enhanced_outcomes"]
200
203
  RSpec.configuration.add_formatter(Inspec::Formatters::ShowProgress, $stderr) if @conf[:show_progress]
201
204
  set_optional_formatters
@@ -24,12 +24,18 @@ module Secrets
24
24
  @inputs = ::YAML.load_file(target)
25
25
  end
26
26
 
27
- if @inputs == false || !@inputs.is_a?(Hash)
28
- Inspec::Log.warn("#{self.class} unable to parse #{target}: invalid YAML or contents is not a Hash")
27
+ # In case of empty yaml file raise the warning else raise the parsing error.
28
+ if !@inputs || @inputs.empty?
29
+ Inspec::Log.warn("Unable to parse #{target}: YAML file is empty.")
29
30
  @inputs = nil
31
+ elsif !@inputs.is_a?(Hash)
32
+ # Exits with usage error.
33
+ Inspec::Log.error("Unable to parse #{target}: invalid YAML or contents is not a Hash")
34
+ Inspec::UI.new.exit(:usage_error)
30
35
  end
31
36
  rescue => e
32
- raise "Error reading InSpec inputs: #{e}"
37
+ # Any other error related to Yaml parsing will be raised here.
38
+ raise "Error reading YAML file #{target}: #{e}"
33
39
  end
34
40
  end
35
41
  end
data/lib/inspec/shell.rb CHANGED
@@ -1,3 +1,6 @@
1
+ require "chef-licensing"
2
+ require "inspec/dist"
3
+
1
4
  autoload :Pry, "pry"
2
5
 
3
6
  module Inspec
@@ -10,6 +13,7 @@ module Inspec
10
13
  end
11
14
 
12
15
  def start
16
+ ChefLicensing.check_software_entitlement! if Inspec::Dist::EXEC_NAME == "inspec"
13
17
  # This will hold a single evaluation binding context as opened within
14
18
  # the instance_eval context of the anonymous class that the profile
15
19
  # context creates to evaluate each individual test file. We want to
@@ -18,6 +22,12 @@ module Inspec
18
22
  @ctx_binding = @runner.eval_with_virtual_profile("binding")
19
23
  configure_pry
20
24
  @ctx_binding.pry
25
+ rescue ChefLicensing::SoftwareNotEntitled
26
+ Inspec::Log.error "License is not entitled to use InSpec."
27
+ Inspec::UI.new.exit(:license_not_entitled)
28
+ rescue ChefLicensing::Error => e
29
+ Inspec::Log.error e.message
30
+ Inspec::UI.new.exit(:usage_error)
21
31
  end
22
32
 
23
33
  def configure_pry # rubocop:disable Metrics/AbcSize
data/lib/inspec/ui.rb CHANGED
@@ -32,9 +32,13 @@ module Inspec
32
32
  EXIT_FATAL_DEPRECATION = 3
33
33
  EXIT_GEM_DEPENDENCY_LOAD_ERROR = 4
34
34
  EXIT_BAD_SIGNATURE = 5
35
+ EXIT_SIGNATURE_REQUIRED = 6
35
36
  EXIT_LICENSE_NOT_ACCEPTED = 172
37
+ EXIT_LICENSE_NOT_ENTITLED = 173
38
+ EXIT_LICENSE_NOT_SET = 174
36
39
  EXIT_FAILED_TESTS = 100
37
40
  EXIT_SKIPPED_TESTS = 101
41
+ EXIT_TERMINATED_BY_CTL_C = 130
38
42
 
39
43
  attr_reader :io
40
44
 
@@ -0,0 +1,9 @@
1
+ require_relative "../log"
2
+ require "chef-licensing"
3
+ ChefLicensing.configure do |config|
4
+ config.chef_product_name = "InSpec"
5
+ config.chef_entitlement_id = "3ff52c37-e41f-4f6c-ad4d-365192205968"
6
+ config.chef_executable_name = "inspec"
7
+ config.license_server_url = "https://services.chef.io/licensing"
8
+ config.logger = Inspec::Log
9
+ end