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.
- checksums.yaml +4 -4
- data/Chef-EULA +9 -0
- data/Gemfile +14 -4
- data/etc/features.sig +6 -0
- data/etc/features.yaml +97 -0
- data/inspec-core.gemspec +17 -7
- data/lib/inspec/backend.rb +2 -0
- data/lib/inspec/base_cli.rb +80 -4
- data/lib/inspec/cached_fetcher.rb +24 -3
- data/lib/inspec/cli.rb +293 -236
- data/lib/inspec/config.rb +24 -2
- data/lib/inspec/dependencies/cache.rb +33 -0
- data/lib/inspec/enhanced_outcomes.rb +1 -0
- data/lib/inspec/errors.rb +5 -0
- data/lib/inspec/exceptions.rb +2 -0
- data/lib/inspec/feature/config.rb +75 -0
- data/lib/inspec/feature/runner.rb +29 -0
- data/lib/inspec/feature.rb +42 -0
- data/lib/inspec/fetcher/git.rb +5 -0
- data/lib/inspec/fetcher/url.rb +24 -4
- data/lib/inspec/globals.rb +6 -0
- data/lib/inspec/iaf_file.rb +3 -2
- data/lib/inspec/input_registry.rb +5 -1
- data/lib/inspec/plugin/v1/plugin_types/fetcher.rb +7 -0
- data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +30 -2
- data/lib/inspec/profile.rb +44 -1
- data/lib/inspec/reporters.rb +67 -54
- data/lib/inspec/resources/nftables.rb +14 -1
- data/lib/inspec/resources/oracledb_session.rb +12 -3
- data/lib/inspec/resources/ssh_config.rb +100 -9
- data/lib/inspec/resources/ssh_key.rb +124 -0
- data/lib/inspec/resources/sshd_active_config.rb +2 -0
- data/lib/inspec/resources/sybase_session.rb +11 -2
- data/lib/inspec/resources.rb +1 -0
- data/lib/inspec/rule.rb +6 -6
- data/lib/inspec/run_data.rb +7 -5
- data/lib/inspec/runner.rb +43 -6
- data/lib/inspec/runner_rspec.rb +12 -9
- data/lib/inspec/secrets/yaml.rb +9 -3
- data/lib/inspec/shell.rb +10 -0
- data/lib/inspec/ui.rb +4 -0
- data/lib/inspec/utils/licensing_config.rb +9 -0
- data/lib/inspec/utils/telemetry/base.rb +149 -0
- data/lib/inspec/utils/telemetry/http.rb +40 -0
- data/lib/inspec/utils/telemetry/null.rb +11 -0
- data/lib/inspec/utils/telemetry/run_context_probe.rb +13 -1
- data/lib/inspec/utils/telemetry.rb +74 -3
- data/lib/inspec/version.rb +1 -1
- data/lib/inspec/waiver_file_reader.rb +68 -27
- data/lib/inspec.rb +2 -2
- data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +189 -168
- data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +10 -3
- data/lib/plugins/inspec-init/lib/inspec-init/cli.rb +1 -0
- data/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb +23 -21
- data/lib/plugins/inspec-init/lib/inspec-init/cli_profile.rb +15 -13
- data/lib/plugins/inspec-init/lib/inspec-init/cli_resource.rb +15 -13
- data/lib/plugins/inspec-license/README.md +16 -0
- data/lib/plugins/inspec-license/inspec-license.gemspec +6 -0
- data/lib/plugins/inspec-license/lib/inspec-license/cli.rb +26 -0
- data/lib/plugins/inspec-license/lib/inspec-license.rb +14 -0
- data/lib/plugins/inspec-parallel/README.md +27 -0
- data/lib/plugins/inspec-parallel/inspec-parallel.gemspec +6 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/child_status_reporter.rb +61 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/cli.rb +39 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/command.rb +219 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb +270 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/base.rb +24 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/silent.rb +7 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/status.rb +125 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/text.rb +23 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/validator.rb +170 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel.rb +18 -0
- data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +20 -8
- data/lib/plugins/inspec-sign/lib/inspec-sign/cli.rb +11 -4
- data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +6 -13
- metadata +61 -19
- data/lib/inspec/utils/telemetry/collector.rb +0 -81
- data/lib/inspec/utils/telemetry/data_series.rb +0 -44
- 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
|
-
|
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
|
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
|
-
|
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
|
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 =
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
@@ -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
|
-
|
49
|
-
|
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
|
data/lib/inspec/resources.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/inspec/run_data.rb
CHANGED
@@ -27,11 +27,13 @@ module Inspec
|
|
27
27
|
) do
|
28
28
|
include HashLikeStruct
|
29
29
|
def initialize(raw_run_data)
|
30
|
-
|
31
|
-
|
32
|
-
self.
|
33
|
-
self.
|
34
|
-
self.
|
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
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
|
data/lib/inspec/runner_rspec.rb
CHANGED
@@ -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
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
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
|
data/lib/inspec/secrets/yaml.rb
CHANGED
@@ -24,12 +24,18 @@ module Secrets
|
|
24
24
|
@inputs = ::YAML.load_file(target)
|
25
25
|
end
|
26
26
|
|
27
|
-
|
28
|
-
|
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
|
-
|
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
|